OPTEE之安全存储详解

安全存储是什么?

OP-TEE的安全存储功能是OP-TEE为用户提供的安全存储机制。
用户可使用安全存储功能来保存敏感数据、密钥等信息。
使用OP-TEE安全存储功能保存数据时,OP-TEE会对需要被保存的数据进行加密,且每次更新安全文件时所用的加密密钥都会使用随机数重新生成,用户只要调用GP标准中定义的安全存储相关接口就能使用OP-TEE的安全存储功能对私有数据进行保护。

需要被保护的数据被OP-TEE加密后会被保存到REE侧的文件系统、EMMC的RPMB分区或数据库中,至于具体需要将加密后的数据保存到哪里则由芯片提供商决定。

安全存储功能可提供一个安全的存储环境,安全文件中数据的加解密过程都在OP-TEE中完成,且加解密密钥的生成也是在OP-TEE中进行的,这样就能保证数据的安全性。

二、安全存储相关技术点

1.加载dirf.db文件

我们以打开安全文件为例,跟踪学习一下OPTEE安全存储相关的调用流程。

@tee_ree_fs.c
提供了基本的文件操作接口

const struct tee_file_operations ree_fs_ops = {
	.open = ree_fs_open,
	.create = ree_fs_create,
	.close = ree_fs_close,
	.read = ree_fs_read,
	.write = ree_fs_write,
	.truncate = ree_fs_truncate,
	.rename = ree_fs_rename,
	.remove = ree_fs_remove,
	.opendir = ree_fs_opendir_rpc,
	.closedir = ree_fs_closedir_rpc,
	.readdir = ree_fs_readdir_rpc,
};

我们看一下其中的open函数ree_fs_open

static TEE_Result ree_fs_open(struct tee_pobj *po, size_t *size,
			      struct tee_file_handle **fh)
{
...
	res = get_dirh(&dirh);
	res = ree_fs_open_primitive(false, dfh.hash, &po->uuid, &dfh, fh);

...
}

通过ree_fs_dirh_refcount全局变量 来控制是创建dirf.db文件,还是打开dirf.db文件

static TEE_Result get_dirh(struct tee_fs_dirfile_dirh **dirh)
{
	if (!ree_fs_dirh) {
		TEE_Result res = open_dirh(&ree_fs_dirh);

		if (res) {
			*dirh = NULL;
			return res;
		}
	}
	ree_fs_dirh_refcount++;
	assert(ree_fs_dirh);
	assert(ree_fs_dirh_refcount);
	*dirh = ree_fs_dirh;
	return TEE_SUCCESS;
}

@fs_dirfile.c

TEE_Result tee_fs_dirfile_open(bool create, uint8_t *hash,
			       const struct tee_fs_dirfile_operations *fops,
			       struct tee_fs_dirfile_dirh **dirh_ret)
{
...
	dirh->fops = fops;
	res = fops->open(create, hash, NULL, NULL, &dirh->fh);
...
}

static const struct tee_fs_dirfile_operations ree_dirf_ops = {
	.open = ree_fs_open_primitive,
	.close = ree_fs_close_primitive,
	.read = ree_fs_read_primitive,
	.write = ree_fs_write_primitive,
	.commit_writes = ree_dirf_commit_writes,
};

ree_fs_open_primitive函数

static TEE_Result ree_fs_open_primitive(bool create, uint8_t *hash,
					const TEE_UUID *uuid,
					struct tee_fs_dirfile_fileh *dfh,
					struct tee_file_handle **fh)
{
	struct tee_fs_fd *fdp;
	fdp = calloc(1, sizeof(struct tee_fs_fd));

	fdp->fd = -1;
	fdp->uuid = uuid;

	if (create)
		res = tee_fs_rpc_create_dfh(OPTEE_RPC_CMD_FS,
					    dfh, &fdp->fd);
	else
		res = tee_fs_rpc_open_dfh(OPTEE_RPC_CMD_FS, dfh, &fdp->fd);
		
	res = tee_fs_htree_open(create, hash, uuid, &ree_fs_storage_ops,
				fdp, &fdp->ht);
}

@fs_htree.c
读文件时,主要是通过如下代码实现的,其中有校验的部分,请查看如下代码注释部分。

TEE_Result tee_fs_htree_open(bool create, uint8_t *hash, const TEE_UUID *uuid,
			     const struct tee_fs_htree_storage *stor,
			     void *stor_aux, struct tee_fs_htree **ht_ret)
{
	TEE_Result res;
	struct tee_fs_htree *ht = calloc(1, sizeof(*ht));
	ht->uuid = uuid;
	ht->stor = stor;
	ht->stor_aux = stor_aux;

	if (create) {
		const struct tee_fs_htree_image dummy_head = { .counter = 0 };
		res = crypto_rng_read(ht->fek, sizeof(ht->fek));
		res = tee_fs_fek_crypt(ht->uuid, TEE_MODE_ENCRYPT, ht->fek,
				       sizeof(ht->fek), ht->head.enc_fek);
		res = init_root_node(ht);
		ht->dirty = true;
		res = tee_fs_htree_sync_to_storage(&ht, hash);
		res = rpc_write_head(ht, 0, &dummy_head);
	} else {
		#读取dirf.db文件,调用rpc_read_node rpc_read读取dirf.db中的root node信息
		#其中函数调用过程:init_head_from_data -> rpc_read_node -> rpc_read
		res = init_head_from_data(ht, hash);
		
		#解密出root node内容并校验
		res = verify_root(ht);
		
		#读取dirf.db中所有node信息并创建文件的节点树
		res = init_tree_from_data(ht);
		
		#计算各节点内容的hash值,并与保存的hash进行比对,来校验整个hashtree是否合法
		res = verify_tree(ht);
	}
...
}

其中verify_root函数内容如下:

static TEE_Result verify_root(struct tee_fs_htree *ht)
{
	TEE_Result res;
	void *ctx;

	res = tee_fs_fek_crypt(ht->uuid, TEE_MODE_DECRYPT, ht->head.enc_fek,
			       sizeof(ht->fek), ht->fek);
	if (res != TEE_SUCCESS)
		return res;

	res = authenc_init(&ctx, TEE_MODE_DECRYPT, ht, NULL, sizeof(ht->imeta));
	if (res != TEE_SUCCESS)
		return res;

	return authenc_decrypt_final(ctx, ht->head.tag, ht->head.imeta,
				     sizeof(ht->imeta), &ht->imeta);
}

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

2. dirf.db文件和安全存储文件的格式

在了解了基本的流程后,我们接下来来扣一下细节,查看一下文件的格式以及结构体的定义等。

2.1 安全文件的三个区域

使用安全存储功能生成的文件都会使用相同的格式被保存,而且dirf.db文件与安全文件的格式也相同。
安全文件中的内容分为三个区域,分别用于保存文件头、结点、数据,文件的内容。
描述如下:
在这里插入图片描述
有三个重要的结构体:
tee_fs_htree_node_image:用于保存文件的节点node信息,通过节点可找到对应文件的头部或数据块信息;tee_fs_htree_image:用于保存安全文件的头部数据,从头部数据中可获取安全文件的加密密钥和加密头部时使用的IV值; tee_fs_fd:安全存储操作时使用的重要结构体,存放对文件操作时使用的fd、dir、TA的UUID等信息。

结构体字段的定义如下:
@core/include/tee/fs_htree.h


struct tee_fs_htree_meta {
	uint64_t length;
};

struct tee_fs_htree_imeta {
	struct tee_fs_htree_meta meta;
	uint32_t max_node_id;
};

struct tee_fs_htree_image {
	uint8_t iv[TEE_FS_HTREE_IV_SIZE];
	uint8_t tag[TEE_FS_HTREE_TAG_SIZE];
	uint8_t enc_fek[TEE_FS_HTREE_FEK_SIZE];
	uint8_t imeta[sizeof(struct tee_fs_htree_imeta)];
	uint32_t counter;
};

struct tee_fs_htree_node_image {
	uint8_t hash[TEE_FS_HTREE_HASH_SIZE];
	uint8_t iv[TEE_FS_HTREE_IV_SIZE];
	uint8_t tag[TEE_FS_HTREE_TAG_SIZE];
	uint16_t flags;
};

2.2 重要结构体的关系框图

这些重要的结构体的关系框图如下:
在这里插入图片描述
其中tee_fs_htree_storage 结构体定义如下,包含了读写RPC调用的函数指针。

struct tee_fs_htree_storage {
	size_t block_size;
	TEE_Result (*rpc_read_init)(void *aux, struct tee_fs_rpc_operation *op,
				    enum tee_fs_htree_type type, size_t idx,
				    uint8_t vers, void **data);
	TEE_Result (*rpc_read_final)(struct tee_fs_rpc_operation *op,
				     size_t *bytes);
	TEE_Result (*rpc_write_init)(void *aux, struct tee_fs_rpc_operation *op,
				     enum tee_fs_htree_type type, size_t idx,
				     uint8_t vers, void **data);
	TEE_Result (*rpc_write_final)(struct tee_fs_rpc_operation *op);
};

2.3 FEK和IV介绍

有几个重要的概念 要先介绍一下:

 1. SSK
 	Secure Storage Key 安全存储密钥,在每台设备中都不一样(一般和chipID进行绑定)。
 2. TSK
 	TrustedApplication Storage Key 可信应用的存储密钥,TSK是使用SSK作为密钥对TA的UUID经HMAC运算得到的。
 3. FEK
 	File Encryption Key 是安全存储功能用于对数据进行加密时使用的AES密钥。

使用场景:
FEK + IV值 ==》加密密文数据

这几个KEY的的使用关系:

ChipID + HUK    ==》 HMAC运算 ==》 SSK
SSK    + UUID   ==》 HMAC运算 ==》 TSK
TSK    + RANDOM ==》 AES_CBC ==》 Encryption_FEK

小结:总的来说OPTEE这么设计,主要是想利用两个文件来完成校验功能,一个是加密的安全文件,一个是全局的dirf.db文件。
每次想操作安全文件时,会先从dirf.db中找关键信息,同时做加解密和校验工作,来保证数据的安全性。

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

3. 安全的基本操作

了解了以上基本概述后,我们接下来看一下详细的安全文件的操作,看下是如何处理安全文件的数据的。
因为OPTEE中没有文件系统的功能,需要借助tee_supplicant守护进程来完成访问文件系统的工作。
读写安全文件也是类似的过程,TA发送RPC请求给tee_supplicant,tee_supplicant完成参数的解析和数据操作。

3.1 安全文件的创建

dirf.db文件的创建
在创建dirf.db文件过程中会产生一个随机数作为FEK,且在调用update_root函数时会产生另外一个随机数作为加密FEK的IV值并保存到head.iv中。每次文件的更新时,该IV值都会被新的随机数替代。

安全文件的创建
在TA中调用TEE_CreatePersistentObject接口时会创建安全文件。在创建安全文件时会初始化安全文件的数据区域。
安全文件创建完成之后,会将初始化数据加密后写入到安全文件中,然后更新整个安全文件的tee_fs_htree_node_image区域以及保存在文件头的tee_fs_htree_image区域,到此安全文件创建就已完毕。
为后续能够通过dirf.db文件找到该安全文件,则还需要更新dirf. db文件的内容,主要是更新dirf.db文件数据区域中的dirfile_entry数据。

3.2 安全文件的读取

TA对安全文件进行读写操作是通过调用TEE_ReadObjectData和TEE_WriteObjectData函数来实现的。这两个函数的执行最终会进入OP-TEE的内核空间中。
在OP-TEE内核空间调用对应的读写接口syscall_storage_obj_read和syscall_storage_obj_write函数来完成对安全文件中数据的读写操作。

看一下相关的代码:
@tee/tee_svc_storage.c
检查访问权限,调用

TEE_Result syscall_storage_obj_read(unsigned long obj, void *data, size_t len,
				    uint64_t *count)
{
...
res = vm_check_access_rights(&utc->uctx, TEE_MEMORY_ACCESS_WRITE,
				     (uaddr_t)data, len);
res = o->pobj->fops->read(o->fh, pos_tmp, data, &bytes);
res = copy_to_user_private(count, &u_count, sizeof(*count));
}

@tee_ree_fs.c

static TEE_Result ree_fs_read(struct tee_file_handle *fh, size_t pos,
			      void *buf, size_t *len)
{
	TEE_Result res;

	mutex_lock(&ree_fs_mutex);
	res = ree_fs_read_primitive(fh, pos, buf, len);
	mutex_unlock(&ree_fs_mutex);

	return res;
}

static TEE_Result ree_fs_read_primitive(struct tee_file_handle *fh, size_t pos,
					void *buf, size_t *len)
{
...
	struct tee_fs_htree_meta *meta = tee_fs_htree_get_meta(fdp->ht);
	#计算安全文件数据区域中的block
	start_block_num = pos_to_block_num(pos);
	end_block_num = pos_to_block_num(pos + remain_bytes - 1);

	block = get_tmp_block();
	#循环读取数据
	while (start_block_num <= end_block_num) {
		size_t offset = pos % BLOCK_SIZE;
		size_t size_to_read = MIN(remain_bytes, (size_t)BLOCK_SIZE);

		if (size_to_read + offset > BLOCK_SIZE)
			size_to_read = BLOCK_SIZE - offset;
        #使用IV和FEK解密数据
		res = tee_fs_htree_read_block(&fdp->ht, start_block_num, block);
		if (res != TEE_SUCCESS)
			goto exit;

		memcpy(data_ptr, block + offset, size_to_read);

		data_ptr += size_to_read;
		remain_bytes -= size_to_read;
		pos += size_to_read;

		start_block_num++;
	}
	res = TEE_SUCCESS;

3.3 安全文件的写入

写数据和读数据流程是类似的
@tee_fs_fs.c

static TEE_Result ree_fs_write(struct tee_file_handle *fh, size_t pos,
			       const void *buf, size_t len)
{
...
	res = get_dirh(&dirh);
	res = ree_fs_write_primitive(fh, pos, buf, len);
...
}

static TEE_Result ree_fs_write_primitive(struct tee_file_handle *fh, size_t pos,
					 const void *buf, size_t len)
{
...
	file_size = tee_fs_htree_get_meta(fdp->ht)->length;
	if ((pos + len) < len)
		return TEE_ERROR_BAD_PARAMETERS;

	if (file_size < pos) {
		res = ree_fs_ftruncate_internal(fdp, pos);
		if (res != TEE_SUCCESS)
			return res;
	}

	return out_of_place_write(fdp, pos, buf, len);
}

static TEE_Result out_of_place_write(struct tee_fs_fd *fdp, size_t pos,
				     const void *buf, size_t len)
{

	size_t start_block_num = pos_to_block_num(pos);
	size_t end_block_num = pos_to_block_num(pos + len - 1);

	struct tee_fs_htree_meta *meta = tee_fs_htree_get_meta(fdp->ht);
	block = get_tmp_block();
	while (start_block_num <= end_block_num) {
		size_t offset = pos % BLOCK_SIZE;
		size_t size_to_write = MIN(remain_bytes, (size_t)BLOCK_SIZE);

		if (size_to_write + offset > BLOCK_SIZE)
			size_to_write = BLOCK_SIZE - offset;

		if (start_block_num * BLOCK_SIZE <
		    ROUNDUP(meta->length, BLOCK_SIZE)) {
			res = tee_fs_htree_read_block(&fdp->ht,
						      start_block_num, block);
			if (res != TEE_SUCCESS)
				goto exit;
		} else {
			memset(block, 0, BLOCK_SIZE);
		}

...
			memset(block + offset, 0, size_to_write);

		res = tee_fs_htree_write_block(&fdp->ht, start_block_num,
					       block);
...
	}

	if (pos > meta->length) {
		meta->length = pos;
		tee_fs_htree_meta_set_dirty(fdp->ht);
	}

...
}

@fs_htree.c

TEE_Result tee_fs_htree_write_block(struct tee_fs_htree **ht_arg,
				    size_t block_num, const void *block)
{
...

	res = get_block_node(ht, true, block_num, &node);
	res = ht->stor->rpc_write_init(ht->stor_aux, &op,
				       TEE_FS_HTREE_TYPE_BLOCK, block_num,
				       block_vers, &enc_block);

	res = authenc_init(&ctx, TEE_MODE_ENCRYPT, ht, &node->node,
			   ht->stor->block_size);

	res = authenc_encrypt_final(ctx, node->node.tag, block,
				    ht->stor->block_size, enc_block);

	res = ht->stor->rpc_write_final(&op);
...
}

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

4. 安全存储中的加密解密

安全存储中的安全文件和dirf.db文件中的数据内容都是按照一定的格式保存的,
主要由三部分组成:tee_fs_htree_image、tee_fs_htree_node_image和数据区域块。

tee_fs_htree_image和tee_fs_htree_node_image结构体中保存的是安全文件操作时使用到的重要数据的密文数据;tee_fs_htree_image区域中的数据是对元数据经加密重要数据后生成的,而数据区域块和tee_fs_htree_node_image中的数据则是对数据块数据经加密后获得的。

4.1 数据结构的组成与作用

tee_fs_htree_image主要保存加密头部的IV值、加密安全文件的FEK使用的enc_fek以及加密之后生成的tag、imeta及标记两个tee_fs_htree_image哪个为最新的counter值。

tee_fs_htree_node_image保存节点的哈希值、加密数据块区域使用的IV值、标记使用哪个data block的ver的flag值以及加密需要被保存的数据时生成的tag数据。数据块区域保存的是需要被保存的数据的密文数据。

tee_fs_htree_image中的imeta是按照元数据的方式经加密对应的数据获得,tee_fs_htree_node_imaget中的tag跟数据块中的数据则是按照数据块加密策略经加密后获得。

4.2 元数据的加密

tee_fs_htree_image区域中的数据是按照元数据方式经加密生成的,该加密过程如图:
在这里插入图片描述

FEK:安全文件和dirf.db文件在执行加密操作时使用的密钥,该值在文件创建时使用随机数的方式生成。对已经创建好的文件进行操作时,该值会从tee_fs_htree_image的enc_fek成员中使用TSK解密获得;
TSK:使用SSK和UUID执行HMAC计算得到;
AES_ECB:将FEK使用TSK经AES的ECB模式加密操作后生成enc_fek;
Encrypted FEK:使用TSK加密FEK得到,保存在tee_fs_htree_image的enc_fek中,最终会被写入安全文件或者dirf.db文件头的头部中;
Meta IV:使用安全存储创建文件或将tee_fs_htree_image写入文件中都会被随机生成,最终会被写入安全文件或dirf.db文件头的头部中;
Meta Data:/data/tee目录下每个文件中存放的tee_fs_htree_node_image的个数相关的数据;
AES_GCM:将enc_fek+meta iv+meta data使用FEK和meta IV进行AES的GCM模式加密操作生成tag和Encryption Meta Data数据;
Tag:加密enc_fek+meta iv+meta data时生成的tag值,数据会被保存在tee_fs_htree_image中的tag成员中;Encryptoed Meta Data:加密enc_fek+meta iv+meta data时生成的imeta值,数据会被保存在tee_fs_htree_image中的imeta成员中。

4.3 数据块数据的加密

数据块区域和tee_fs_htree_node_image中的数据是按照数据块区域的加密策略经加密明文数据生成的,数据块区域加密策略的加密过程如图:
在这里插入图片描述

Encrypted FEK:使用TSK加密FEK得到,保存在tee_fs_htree_image的enc_fek中,最终会被写入安全文件或者dirf.db文件头的头部中;
TSK:使用SSK和UUID执行HMAC计算得到;AES_ECB:将Encrypted FEK使用TSK进行ECB模式的AES解密操作生成FEK;FEK:解密Encrypted FEK之后生成的FEK,用于加密需要被保存的数据块;
Block IV:每次加密数据区域中每个数据块是都会随机生成,然后被保存到tee_fs_htree_node_image变量的IV成员中;
Block Data:将需要被保存的数据更新到对应的数据块区域,然后重新加密后生成新的数据块的密文数据;
AES_GCM:将Block IV+Block data使用FEK和块IV进行GCM模式的AES加密操作生成tag和Encryption Block Data数据;
Tag:加密Block IV+Block data时生成的tag值,数据会被保存在tee_fs_htree_node_image中的tag成员中;
Encryption Block Data:加密Block IV+Block data时生成的Encryption BlockData值,数据会被保存在文件中数据区域对应的block中。

总结

安全存储功能是OP-TEE中的一个重要功能,为用户提供一个安全存取数据的方式。由于每次在对安全文件进行写入操作时都会使用随机数重新生成加密时使用的IV值,且加密时使用的密钥也在创建安全文件时使用随机数生成,并被加密保存到安全文件的头部中,所以很难非法获取到安全存储中保存的明文数据。
内部实现的数据操作比较复杂,原理和RPMB的存储有点类似,读写数据采用动态变化的参数如IV值或者Counter计数器值,防止重放或者暴力尝试破解。

参考资料
《手机安全和可信应用开发指南》

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值