没搭建qemu运行ATF+Linux运行环境的,赶紧搞起来:XXX
上一篇介绍了fip包的加载流程,说明了固件信任链,加解密验签的流程本篇开始展开,有代码,可操作,不只说空话翻译外文文档,真正触摸到代码才是真实,实力派扛把子。
1. 验签基础知识
1.1 整体流程
安全引导(Secure Boot)功能是指在系统的整个启动过程中,使用链式验证电子签名的方式来验证系统中重要镜像文件的可靠性,然后再加载镜像文件的引导过程。
Bootloader、Linux内核、TEE OS的启动都由ATF来加载和引导。对于ARMv8, Bootloader、Linux内核和TEE OS镜像文件的验签工作都是在ATF中完成的。
ATF冷启动实现分为5个步骤:
- BL1 - AP Trusted ROM,一般为BootRom。
- BL2 - Trusted Boot Firmware,一般为Trusted Bootloader。
- BL31 - EL3 Runtime Firmware,一般为SML,管理SMC执行处理和中断,运行在secure monitor中。
- BL32 - Secure-EL1 Payload,一般为TEE OS Image。
- BL33 - Non-Trusted Firmware,一般为uboot、linux kernel。
- 系统启动过程中的第一级验签操作是由ChipRom来完成的,只要芯片一出厂,用户就无法修改固化在芯片中的这部分代码。
- 验签操作使用的RSA公钥或者哈希值将会被保存在OTP/efuse中,该区域中的数据一般只有ChipRom和TEE能够读取且无法被修改。RSA公钥或者哈希值将会在产品出厂之前被写入到OTP/efuse中,而且不同厂商使用的密钥会不一样。
- RSA公钥或者哈希值,需要保存在电子证书中,系统启动先对电子证书进行验证,验证证书通过后再去验证镜像文件的合法性。但是一般芯片厂商直接把其放入到了fip包中。
- ARMv8架构之后ARM提供了ATF, BootLoader、TEE镜像文件、Linux内核镜像文件、recovery镜像文件都是由ATF来进行引导和加载而不是由ChipRom来完成的。当然开发者也可以对ATF进行定制化,修改ATF中的验签过程,但是修改后的验签方案需要符合TBBR规范(https://developer.arm.com/documentation/den0006/d/)。
- ChipRom只会去验证ATF中bl1的合法性,后续引导过程同样也是按照链式验签的方式进行,符合TBBR规范。系统启动的地址只能是ChipRom,调试阶段可以使用xip启动模式,正式产品可以通过OTP或EFUSE中特定bit实现关xip启动模式,只能从Rom启动。
OTP和efuse是什么?
- 一次性可编程存储器 (One-Time Programmable Memory):OTP 是一种只能写入数据一次的存储器类型。一旦写入数据,就不能更改或擦除。这使得它非常适合存储像安全密钥、设备配置或启动模式选择等敏感信息。
- 电子熔丝 (Electronic Fuse):eFuse 是一种可以通过编程方式 “烧断” 的电子元件。烧断 eFuse 后,其状态将永久改变,无法恢复。
TBBR规范是什么?
ARM官方的secure boot实现规范TBB(Trusted Board Boot)的规格说明书TBBR(TBB Requirements):https://developer.arm.com/documentation/den0006/d/。其他厂商可以去参考。
1.2 算法介绍
消息摘要算法是通过单向散列函数,将一段消息的内容转换为另一段固定长度消息的过程,而被其计算后生成的消息称为hash值,它具有以下特征:
- 任意长度的输入消息都会输出固定长度的hash值
- hash函数必须具备单向性,而无法通过hash值反向算出其消息原文
- 不同的输入消息其输出消息也不同,即其具有强抗碰撞性
- hash值的计算速度要快
在密码学中有多种消息摘要算法,如md5、sha1、sha256、sha512、sha3等。随着计算机技术的发展,有些原先认为具有强碰撞性的算法(如md5、sha1),在当前已经被认为并不安全,因此secureboot中一般使用sha256、sha512等算法作为完整性算法
非对称密码算法是相对于对称性密码算法而言的,在对称密码算法中加密和解密时使用的密钥相同。而非对称算法加密和解密时使用的密钥不同,其中私钥由密钥属主保管,且不能泄露,而公钥可以分发给其它人。通过私钥签名的数据只能由公钥验签。
镜像签名流程:
- 使用hash算法生成镜像的hash值hash(image)
- 通过镜像发布者的私钥,使用非对称算法对镜像的hash值执行签名流程,并生成其签名值sig(hash)
镜像验签流程:
- 使用非对称算法的公钥和签名值,对镜像的hash值进行验签。若验签通过则可进一步校验镜像完整性。否则,启动失败
- 若验签通过,则重新计算镜像的hash值hash(image),并将其与原始hash值hash(image)比较,若其相等则表明镜像的完整性验证通过。否则启动失败
公钥保护
如果黑客自己造了一堆公钥和私钥,芯片启动公钥的被替换成黑客自己的公钥,黑客用自己的私钥加密自己的镜像进行验签,那就可以绕过去了。
所以公钥不能被替换,这里又要请OTP/efuse出山帮忙了,但是一般公钥比较长,例如RSA的2048bit,一般把公钥的hash值放入OTP/efuse,而公钥还放入镜像中。这样启动时先验证公钥再进行验签。
1.3 数字证书
网络上为了防止公钥被某个节点替换,使用数字证书对发送者的公钥做认证。数字证书格式遵循ITUTX.509标准,其基于ASN.1编码,X509证书包含以下内容:
(1)证书版本
(2)证书序列号
(3)证书所使用的签名算法
(4)证书的发行机构名称,一般采用X500格式
(5)证书的有效期,通常采用UTC时间
(6)证书所有人名称,一般采用X500格式
(7)证书所有人的公钥
(8)证书发行者对证书的签名
数字证书是由数字证书认证中心(CA)颁发,用于认证证书持有者身份的,其核心是使用认证中心的私钥对证书申请人身份(主要是公钥)进行签名认证。认证中心可以形成以下的层级结构,即根证书认证机构可为二级认证机构颁发证书,二级认证机构可为三级认证机构颁发证书,同时他们都可以为用户颁发证书。
本质上,数字证书是数字证书认证中心对证书申请者的信息和公钥做签名,以自身的权威性为其身份做背书。 以上数字证书的层级结构叫做数字证书链,其中顶层的证书被称为根证书,它是由根证书中心自签名的。在验证数字证书时可按照该证书链逐级验证证书的合法性。根证书的合法性是由其自身保证的,因此一般会被预先安装到操作系统或浏览器等软件中。
所有公司都可以颁发数字证书,公司颁发的证书不一定能在国际上得到广泛认可。但若在自己生产的终端中安装自己建立的CA根证书,则可以建立自身的CA信任链。
2. ATF中的验签基础
2.1 基本概念及缩写
- 加密模块CM(Crypto Module):提供数字签名验证和hash验证的接口。
- 加密库CL(Cryptographic Libraies):算法部分。
- 身份验证模块AM(Auth Module):描述和定义CoT,内存分配,记录,提供平台依赖接口
- 镜像解析模块IPM(Image Parser Module):检查镜像的完整性,提取CoT参数。
- 镜像解析库IPL(Image Parser Libraries):对应不同的镜像类型
- 解码库ASN.1:
信任链CoT(Chain of Trust):一系列有联系镜像的集合,身份验证都是围绕CoT来实现的。 信任根公钥ROTPK(Root Public Key):一般把其SHA-256哈希值存储在OTP或者efuse一次性编程存储器中。在开发阶段,ROTPK可以嵌入到bin文件中。
以BL31为例,其身份验证流程为:
- 使用ROTPK公钥或者ROTPK Hash对Trusted Key Certificate进行验证,获取Trusted World Public Key
- 使用Trusted World Public Key对BL31 Key Certificate进行验证,获取BL31 Key Certificate PK
- 使用BL31 Key Certificate PK对BL31 Contend Certificate进行验证获取BL31的hash值
- 使用BL31的hash对BL31镜像进行校验
2.2 CoT镜像身份描述符
CoT是一个结构体数组,元素为auth_img_desc_t,在include/drivers/auth/auth_mod.h中定义:
typedef struct auth_img_desc_s {
unsigned int img_id;
const struct auth_img_desc_s *parent;
img_type_t img_type;
const auth_method_desc_t *const img_auth_methods;
const auth_param_desc_t *const authenticated_data;
} auth_img_desc_t;
- img_id:由平台指定的唯一标识符,它允许IO框架在FIP中定位图像并将其加载到为数据保留的内存中 在CoT中的镜像。
- parent:父镜像的身份验证描述符指针,父镜像中有验证当前镜像的参数,如果为NULL,则从平台或者ROPTK获取,并且其为信任链根。 -img_type:AM用来找到适当的IPM的解析方法,例如IMG_RAW/IMG_PLAT/IMG_CERT3。 -img_auth_methods:验证方法及其参数如前一节所述。这些用于验证当前镜像。
- authenticated_data:参数,用于验证当前CoT中的下一个镜像。这些参数只能由身份验证镜像指定,并且可以在验证后从当前镜像中提取。
typedef struct auth_method_desc_s {
auth_method_type_t type;
union {
auth_method_param_hash_t hash;
auth_method_param_sig_t sig;
auth_method_param_nv_ctr_t nv_ctr;
} param;
} auth_method_desc_t;
定义一个CoT:
/* Macro to register a CoT defined as an array of auth_img_desc_t pointers */
#define REGISTER_COT(_cot) \
const auth_img_desc_t *const *const cot_desc_ptr = (_cot); \
const size_t cot_desc_size = ARRAY_SIZE(_cot); \
unsigned int auth_img_flags[MAX_NUMBER_IDS]
2.3 身份验证方法
include/drivers/auth/auth_common.h中定义auth_method_type_t
typedef struct auth_method_desc_s {
auth_method_type_t type;
union {
auth_method_param_hash_t hash;
auth_method_param_sig_t sig;
auth_method_param_nv_ctr_t nv_ctr;
} param;
} auth_method_desc_t;
/*
* The method type defines how an image is authenticated
*/
typedef enum auth_method_type_enum {
AUTH_METHOD_NONE = 0,
AUTH_METHOD_HASH, /* Authenticate by hash matching */
AUTH_METHOD_SIG, /* Authenticate by PK operation */
AUTH_METHOD_NV_CTR, /* Authenticate by Non-Volatile Counter */
AUTH_METHOD_NUM /* Number of methods */
} auth_method_type_t;
用于IPM从镜像中提取这些验证参数,这些参数在CM中会用到。
AUTH_METHOD_HASH:
typedef struct auth_method_param_hash_s {
auth_param_type_desc_t *data; /* Data to hash */
auth_param_type_desc_t *hash; /* Hash to match with */
} auth_method_param_hash_t;
typedef struct auth_param_type_desc_s {
auth_param_type_t type;
void *cookie;
} auth_param_type_desc_t;
typedef struct auth_param_type_desc_s {
auth_param_type_t type;
void *cookie;
} auth_param_type_desc_t;
typedef enum auth_param_type_enum {
AUTH_PARAM_NONE,
AUTH_PARAM_RAW_DATA, /* Raw image data */
AUTH_PARAM_SIG, /* The image signature */
AUTH_PARAM_SIG_ALG, /* The image signature algorithm */
AUTH_PARAM_HASH, /* A hash (including the algorithm) */
AUTH_PARAM_PUB_KEY, /* A public key */
AUTH_PARAM_NV_CTR, /* A non-volatile counter */
} auth_param_type_t;
AUTH_METHOD_SIG:
typedef struct auth_method_param_sig_s {
auth_param_type_desc_t *pk; /* Public key */
auth_param_type_desc_t *sig; /* Signature to check */
auth_param_type_desc_t *alg; /* Signature algorithm */
auth_param_type_desc_t *data; /* Data signed */
} auth_method_param_sig_t;
AUTH_METHOD_NV_CTR(防止回滚的验证,non-volatile计数器,只增加):
typedef struct auth_method_param_nv_ctr_s {
auth_param_type_desc_t *cert_nv_ctr; /* NV counter in certificate */
auth_param_type_desc_t *plat_nv_ctr; /* NV counter in platform */
} auth_method_param_nv_ctr_t;
2.4 验证参数存储
从父镜像提取的子镜像验证参数,需要单独存储,因为当加载子镜像的时候会冲掉父镜像占用的内存,需要使用auth_param_data_desc_t中ptr指针存储。
/*
* Store a pointer to the authentication parameter and its length
*/
typedef struct auth_param_data_desc_s {
void *ptr;
unsigned int len;
} auth_param_data_desc_t;
/*
* Authentication parameter descriptor, including type and value
*/
typedef struct auth_param_desc_s {
auth_param_type_desc_t *type_desc;
auth_param_data_desc_t data;
} auth_param_desc_t;
IPM获取镜像验证参数的时候填充ptr和len。auth_param_desc_t中保护了这个data。
2.5 镜像解析库IPL
AM按照CoT的定义对镜像进行身份验证。当平台层将镜像从存储介质加载到指定内存后,AM模块首先调用IPM中的IPL对镜像进行完整性检查,验证通过后提取子镜像的参数。
所以IPL的功能:检查镜像完整性,提取验证参数。
/*
* Image types. A parser should be instantiated and registered for each type
*/
typedef enum img_type_enum {
IMG_RAW, /* Binary image */
IMG_PLAT, /* Platform specific format */
IMG_CERT, /* X509v3 certificate */
IMG_MAX_TYPES,
} img_type_t;
- IMG_RAW:原始二进制
- IMG_PLAT:平台特定格式
- IMG_CERT:x.509行业标准的PKI证书镜像
IPL的描述和定义:
/* Image parser library structure */
typedef struct img_parser_lib_desc_s {
img_type_t img_type;
const char *name;
void (*init)(void);
int (*check_integrity)(void *img, unsigned int img_len);//镜像完整性检查
int (*get_auth_param)(const auth_param_type_desc_t *type_desc,
void *img, unsigned int img_len,
void **param, unsigned int *param_len);//提取镜像验证函数的指针
} img_parser_lib_desc_t;
/* Macro to register an image parser library */
#define REGISTER_IMG_PARSER_LIB(_type, _name, _init, _check_int, _get_param) \
static const img_parser_lib_desc_t __img_parser_lib_desc_##_type \
__section(".img_parser_lib_descs") __used = { \
.img_type = _type, \
.name = _name, \
.init = _init, \
.check_integrity = _check_int, \ //镜像完整性检查
.get_auth_param = _get_param \ //提取镜像验证函数的指针
}
IMG_RAW不需要IPL,IMG_CERT镜像注册了IPL(X509v3库),使用了mbedTLS开源密码库。
drivers/auth/mbedtls/mbedtls_x509_parser.c中
REGISTER_IMG_PARSER_LIB(IMG_CERT, LIB_NAME, init,
check_integrity, get_auth_param);
2.6 加密库CL数据结构
AM使用IPM对镜像完整性进行检查通过后,调用CM的接口进行身份验证,CM调用CL中函数进行实现
/*
* Cryptographic library descriptor
*/
typedef struct crypto_lib_desc_s {
const char *name;//加密库名字
/* Initialize library. This function is not expected to fail. All errors
* must be handled inside the function, asserting or panicking in case of
* a non-recoverable error */
void (*init)(void);//初始化指针
/* Verify a digital signature. Return one of the
* 'enum crypto_ret_value' options */
int (*verify_signature)(void *data_ptr, unsigned int data_len,
void *sig_ptr, unsigned int sig_len,
void *sig_alg, unsigned int sig_alg_len,
void *pk_ptr, unsigned int pk_len);//签名验证指针
/* Verify a hash. Return one of the 'enum crypto_ret_value' options */
int (*verify_hash)(void *data_ptr, unsigned int data_len,
void *digest_info_ptr, unsigned int digest_info_len);//hash函数验证指针
/* Calculate a hash. Return hash value */
int (*calc_hash)(enum crypto_md_algo md_alg, void *data_ptr,
unsigned int data_len,
unsigned char output[CRYPTO_MD_MAX_SIZE]);//技术hash值函数指针
/* Convert Public key (optional) */
int (*convert_pk)(void *full_pk_ptr, unsigned int full_pk_len,
void **hashed_pk_ptr, unsigned int *hashed_pk_len);
/*
* Authenticated decryption. Return one of the
* 'enum crypto_ret_value' options.
*/
int (*auth_decrypt)(enum crypto_dec_algo dec_algo, void *data_ptr,
size_t len, const void *key, unsigned int key_len,
unsigned int key_flags, const void *iv,
unsigned int iv_len, const void *tag,
unsigned int tag_len);//解密函数指针
} crypto_lib_desc_t;
注册:
/* Macro to register a cryptographic library */
#define REGISTER_CRYPTO_LIB(_name, _init, _verify_signature, _verify_hash, \
_calc_hash, _auth_decrypt, _convert_pk) \
const crypto_lib_desc_t crypto_lib_desc = { \
.name = _name, \
.init = _init, \
.verify_signature = _verify_signature, \ //签名验证
.verify_hash = _verify_hash, \ //hash验证
.calc_hash = _calc_hash, \
.auth_decrypt = _auth_decrypt, \
.convert_pk = _convert_pk \
}
加密库mbedTLS的注册,在drivers/auth/mbedtls/mbedtls_crypto.c中
#define LIB_NAME "mbed TLS"
REGISTER_CRYPTO_LIB(LIB_NAME, init, verify_signature, verify_hash, NULL,
NULL, NULL);
3. 编译实操
3.1 打开镜像验签宏
optee/build/qemu_v8.mk中:
11 ################################################################################
12 # If you change this, you MUST run `make arm-tf-clean` first before rebuilding
13 ################################################################################
14 TF_A_TRUSTED_BOARD_BOOT ?= n
修改TF_A_TRUSTED_BOARD_BOOT ?= y,或者通过编译参数传入都可以,进行重新编译:
make arm-tf-clean
make arm-tf DEBUG=1 && make -f qemu_v8.mk run-only
- nt_fw_content.crt:BL31内容证书
- nt_fw_key.crt:BL31密钥证书
- soc_fw_content.crt:BL31内容证书
- soc_fw_key.crt:BL31密钥证书
- tb_fw.crt:BL2内容证书
- trusted_key.crt:Trusted密钥证书
- rot_key.pem:Root of Trust Key
- rotpk_sha256.bin:ROTPK的sha256值
- 其他:nt_fw_key.crt tos_fw_content.crt tos_fw_key.crt
3.2 BL2的信任链
首先就是BL2的CoT,在drivers/auth/tbbr/tbbr_cot_bl1.c中定义:
/*
* TBBR Chain of trust definition
*/
static const auth_img_desc_t * const cot_desc[] = {
[TRUSTED_BOOT_FW_CERT_ID] = &trusted_boot_fw_cert,
[BL2_IMAGE_ID] = &bl2_image,
[HW_CONFIG_ID] = &hw_config,
[TB_FW_CONFIG_ID] = &tb_fw_config,
[FW_CONFIG_ID] = &fw_config,
[FWU_CERT_ID] = &fwu_cert,
[SCP_BL2U_IMAGE_ID] = &scp_bl2u_image,
[BL2U_IMAGE_ID] = &bl2u_image,
[NS_BL2U_IMAGE_ID] = &ns_bl2u_image
};
/* Register the CoT in the authentication module */
REGISTER_COT(cot_desc);
trusted_boot_fw_cert是一个证书镜像,是BL2的父镜像,可以对BL2进行身份验证。
fwu的u是什么?
fw就是firmware,u是update的意思,是固件升级用的镜像。
include/export/common/tbbr/tbbr_img_def_exp.h中定义了这些镜像ID是唯一的
/* Firmware Image Package */
#define FIP_IMAGE_ID U(0)
/* Trusted Boot Firmware BL2 */
#define BL2_IMAGE_ID U(1)
/* SCP Firmware SCP_BL2 */
#define SCP_BL2_IMAGE_ID U(2)
/* EL3 Runtime Firmware BL31 */
#define BL31_IMAGE_ID U(3)
/* Secure Payload BL32 (Trusted OS) */
#define BL32_IMAGE_ID U(4)
/* Non-Trusted Firmware BL33 */
#define BL33_IMAGE_ID U(5)
/* Certificates */
#define TRUSTED_BOOT_FW_CERT_ID U(6)
#define TRUSTED_KEY_CERT_ID U(7)
#define SCP_FW_KEY_CERT_ID U(8)
#define SOC_FW_KEY_CERT_ID U(9)
#define TRUSTED_OS_FW_KEY_CERT_ID U(10)
#define NON_TRUSTED_FW_KEY_CERT_ID U(11)
...........
- ROTPK认证trusted_boot_fw_cert
- trusted_boot_fw_cert获取BL2 Hash并使用其CoT验签BL2 img
drivers/auth/tbbr/tbbr_cot_common.c中定义了trusted_boot_fw_cert
/* trusted_boot_fw_cert */
const auth_img_desc_t trusted_boot_fw_cert = {
.img_id = TRUSTED_BOOT_FW_CERT_ID,//值是6
.img_type = IMG_CERT,//镜像类型是证书镜像
.parent = NULL, //NULL表示是信任链根
.img_auth_methods = (const auth_method_desc_t[AUTH_METHOD_NUM]) {//定义了两种身份认证方式sig(镜像签名)和nv_ctr(非易失性计数器)
[0] = {
.type = AUTH_METHOD_SIG,
.param.sig = {
.pk = &subject_pk,
.sig = &sig,
.alg = &sig_alg,
.data = &raw_data
}
},
[1] = {
.type = AUTH_METHOD_NV_CTR,
.param.nv_ctr = {
.cert_nv_ctr = &trusted_nv_ctr,
.plat_nv_ctr = &trusted_nv_ctr
}
}
},
.authenticated_data = (const auth_param_desc_t[COT_MAX_VERIFIED_PARAMS]) {//定义了四个验证子镜像的验证参数
[0] = {
.type_desc = &tb_fw_hash,
.data = {
.ptr = (void *)tb_fw_hash_buf,//子镜像参数位置,属于静态缓存
.len = (unsigned int)HASH_DER_LEN
}
},
[1] = {
.type_desc = &tb_fw_config_hash,
.data = {
.ptr = (void *)tb_fw_config_hash_buf,
.len = (unsigned int)HASH_DER_LEN
}
},
[2] = {
.type_desc = &hw_config_hash,
.data = {
.ptr = (void *)hw_config_hash_buf,
.len = (unsigned int)HASH_DER_LEN
}
},
[3] = {
.type_desc = &fw_config_hash,
.data = {
.ptr = (void *)fw_config_hash_buf,
.len = (unsigned int)HASH_DER_LEN
}
}
}
};
BL2的如下:
static const auth_img_desc_t bl2_image = {
.img_id = BL2_IMAGE_ID,//值是1
.img_type = IMG_RAW,//原始二进制镜像,不需要IPL
.parent = &trusted_boot_fw_cert,
.img_auth_methods = (const auth_method_desc_t[AUTH_METHOD_NUM]) {
[0] = {
.type = AUTH_METHOD_HASH,//hash验证
.param.hash = {
.data = &raw_data,//验证对象就是自己的数据本身
.hash = &tb_fw_hash//获取hash的参考值
}
}
}
};
篇幅有限,本篇主要从概念和静态配置代码角度进行说明,下一篇从代码运行时序角度进行深度解析,敬请期待。
参考:
- 万字长文:安全启动之SecureBoot启动吧(小白也能看懂!):https://juejin.cn/post/7267839163502739511#heading-7
- 芯片信息安全(一)安全启动:https://zhuanlan.zhihu.com/p/536007837
- 大话密码技术(五)数字签名与数字证书原理:https://zhuanlan.zhihu.com/p/524010855
后记:
研究软件技术本质上的干货还是代码分析,但是只分析代码,很多看的读者估计一打开就退出了,只适合小部分在电脑上详细验证技术的从业者,这只占一小部分。大部分的读者要么知识扫盲科普,要么就是不是专业干这个的。所以尽量在第一章节来段文字描述,后面的代码分析就适合详细的人看了。
又是干货满满的一篇,感觉学习secure boot还挺有成就感,新知识很多,大家多多支持,分享给你的好朋友。