参考内容来自《手机安全和可信应用开发指南》
1、OP-TEE的安全存储的简介
OP-TEE的安全存储功能是OP-TEE为用户提供的安全存储机制。用户可使用安全存储功能来保存敏感数据、密钥等信息。
使用OP-TEE安全存储功能保存数据时,OP-TEE会对需要被保存的数据进行加密,且每次更新安全文件时所用的加密密钥都会使用随机数重新生成,用户只要调用GP标准中定义的安全存储相关接口就能使用OP-TEE的安全存储功能对私有数据进行保护。
需要被保护的数据被OP-TEE加密后会被保存到REE侧的文件系统、EMMC 的RPMB分区或数据库中,至于具体需要将加密后的数据保存到哪里则由芯片提供商决定。(为啥放在REE侧的文件系统?因为REE侧linux有文件系统啊哈哈哈我猜的)
也可通过打开对应的宏开关,使能对应的保存方式来满足用户的实际需求。安全存储功能可提供一个安全的存储环境,安全文件中数据的加解密过程都在OP-TEE中完成,且加解密密钥的生成也是在OP-TEE中进行的,这样就能保证数据的安全性。
使用实例请看书哦
2、安全存储功能使用的密钥
OP-TEE中使用安全存储功能保存的数据都是使用AES算法进行加密的,加密后的文件被保存在文件系统或RPMB分区。
使用AES算法进行数据加密或解密时需提供密钥和初始化向量IV值。
每个TA在使用安全存储功能保存数据时都会生成一个随机数作为IV值,使用FEK的值作为AES的密钥。FEK的值是OP-TEE对相关数据执行HMAC操作后生成的。(HMAC需要三个输入参数,密钥Key,消息内容,Hash算法;(所以HMAC本身并不是一种Hash算法,因而需要调用现成的Hash算法。) )
FEK值的生成涉及SSK和TSK,本章节将介绍这些密钥的使用和生成过程。相关密钥的关系和生成方式如图20-2所示。
(关于这个秘钥的派生,芯片厂商会自定义)
2.1 安全存储密钥
安全存储密钥(Secure Storage Key, SSK)在每台设备中的值都不同。
OP-TEE启动时会使用芯片ID和HUK经HMAC算法计算来获得该值,并将SSK的值保存在结构体变量tee_fs_ssk的密钥成员中,以备生成其他密钥使用。
工厂生产时会将HUK写入到OTP/efuse中,且正常世界状态无法读取到HUK的值,而芯片ID在芯片出厂后就会被写入到芯片中。
OP-TEE启动过程中会执行tee_fs_init_key_manager函数,该函数使用SSK =HMAC(HUK, message)的方式来生成SSK。该函数的内容如下:
static TEE_Result tee_fs_init_key_manager(void)
{
int res = TEE_SUCCESS;
struct tee_hw_unique_key huk;
uint8_t chip_id[TEE_FS_KM_CHIP_ID_LENGTH];
uint8_t message[sizeof(chip_id) + sizeof(string_for_ssk_gen)];
/* SSK的产生:
* SSK = HMAC(HUK, message)
* message := concatenate(chip_id, static string)
* */
/* 获取HUK的值(该接口的实现与平台有关,不同的芯片具有不同读取HUK值的方式)*/
tee_otp_get_hw_unique_key(&huk);
/* 获取芯片ID的值(不同的芯片具有不同的读取芯片ID值的方式)*/
tee_otp_get_die_id(chip_id, sizeof(chip_id));
/* 将chip id + string_for_ssk_gen连接后的值保存到message中,string_for_ssk_gen
是一个静态的字符串,该值被写死在代码中 */
memcpy(message, chip_id, sizeof(chip_id));
memcpy(message + sizeof(chip_id), string_for_ssk_gen,
sizeof(string_for_ssk_gen));
/* 使用huk的值对message的内容做HMAC运算,将获取到的数据作为SSK保存到tee_fs_ssk变
量的key成员中 */
res = do_hmac(tee_fs_ssk.key, sizeof(tee_fs_ssk.key),
huk.data, sizeof(huk.data),
message, sizeof(message));
/* 标记ssk已经生产 */
if (res == TEE_SUCCESS)
tee_fs_ssk.is_init = 1;
return res;
}
2.2 可信应用的存储密钥
可信应用的存储密钥(Trusted Applicant Storage Key, TSK)是生成FEK时使用到的密钥。
TSK是使用SSK作为密钥对TA的UUID经HMAC计算获得,类似于HMAC(SSK, UUID)的方式生成TSK。
在调用tee_fs_fek_crypt函数时会计算TSK的值。TSK最终会被用来生成FEK, FEK会在使用安全存储功能保存数据时被用来加密数据。
2.3 文件加密密钥
文件加密密钥(File Encryption Key, FEK)是安全存储功能用于对数据进行加密时使用的AES密钥,该密钥在生成文件时会使用PRNG算法随机产生,产生的FEK会使用TSK进行加密,然后保存到head.enc_fek变量中。
(PRNGPRNG(pseudorandom number generator)伪随机数生成器是指通过特定算法生成一系列的数字,使得这一系列的数字看起来是随机的,但是实际是确定的,所以叫伪随机数。)
TA在每次使用安全存储功能创建一个安全文件时就会生成一个随机数作为FEK,即每个TA 中的每个安全文件都有一个FEK用于加密对应文件中的数据。(安全加倍啊)
关于FEK的产生可简单理解为如下公式,使用的初始化向量IV值为0:
AES_CBC(in_key, TSK)
OP-TEE通过调用tee_fs_fek_crypt函数来生成一个FEK,该函数代码如下:
TEE_Result tee_fs_fek_crypt(const TEE_UUID *uuid, TEE_OperationMode mode,
const uint8_t *in_key, size_t size,
uint8_t *out_key)
{
TEE_Result res;
uint8_t *ctx = NULL;
size_t ctx_size;
uint8_t tsk[TEE_FS_KM_TSK_SIZE];
uint8_t dst_key[size];
/* 检查输入的用于生成FEK的随机数in_key和用于存放生成的out_key地址是否合法 */
if (! in_key || ! out_key)
return TEE_ERROR_BAD_PARAMETERS;
/* 检查in_key长度 */
if (size ! = TEE_FS_KM_FEK_SIZE)
return TEE_ERROR_BAD_PARAMETERS;
/* 判定SSK是否已经被初始化 */
if (tee_fs_ssk.is_init == 0)
return TEE_ERROR_GENERIC;
/* 如果调用时参数uuid不为0,则调用HMAC算法生成TSK。如果UUID的值为0,则默认生成TSK
使用的原始数据为0 */
if (uuid) {
res = do_hmac(tsk, sizeof(tsk), tee_fs_ssk.key,
TEE_FS_KM_SSK_SIZE, uuid, sizeof(*uuid));
if (res ! = TEE_SUCCESS)
return res;
} else {
uint8_t dummy[1] = { 0 };
res = do_hmac(tsk, sizeof(tsk), tee_fs_ssk.key,
TEE_FS_KM_SSK_SIZE, dummy, sizeof(dummy));
if (res ! = TEE_SUCCESS)
return res;
}
/* 获取调用AEC_CBC操作需要的context的大小 */
res = crypto_ops.cipher.get_ctx_size(TEE_FS_KM_ENC_FEK_ALG, &ctx_size);
if (res ! = TEE_SUCCESS)
return res;
/* 分配一份进行AES_CBC操作时需要的context空间 */
ctx = malloc(ctx_size);
if (! ctx)
return TEE_ERROR_OUT_OF_MEMORY;
/* 使用TSK作为进行AES_CBC计算使用的key,而IV值默认为0 */
res = crypto_ops.cipher.init(ctx, TEE_FS_KM_ENC_FEK_ALG, mode, tsk,
sizeof(tsk), NULL, 0, NULL, 0);
if (res ! = TEE_SUCCESS)
goto exit;
/* 将输入的in_key填充到context中,做完AES_CBC操作之后,输出的数据将会被保存到dst_
key中 */
res = crypto_ops.cipher.update(ctx, TEE_FS_KM_ENC_FEK_ALG,
mode, true, in_key, size, dst_key);
if (res ! = TEE_SUCCESS)
goto exit;
/* 执行AES_CBC的加密运算,生成FEK */
crypto_ops.cipher.final(ctx, TEE_FS_KM_ENC_FEK_ALG);
/* 将生成的FEK的值复制到输出参数中 */
memcpy(out_key, dst_key, sizeof(dst_key));
exit:
free(ctx);
return res;
}
3、安全文件、dirf.db文件的数据格式和操作过程
OP-TEE的安全存储功能可满足用户保存敏感数据的需求,需要被保存的数据会被加密保存到文件系统或RPMB分区中。
当选择将数据保存到文件系统中时,默认情况下,加密后的数据会被保存在/data/tee目录中。
安全存储功能使用二叉树的方式来保存加密后的文件。
当第一次使用安全存储功能创建用于保存敏感数据的安全文件时,OP-TEE将会在/data/tee目录中生成两个文件:dirf.db文件和以数字命名的文件。
dirf.db文件保存的是整个安全存储功能管理的所有文件的目录信息和节点信息。当用户使用某个已经存在的安全文件时,OP-TEE首先会读取dirf.db文件中的相关内容,然后根据需要操作的安全文件名字的哈希值在dirf.db文件中找到对应的文件编号,最终按照这个编号实现对文件的打开、关闭、写入、读出、重命名、裁剪等操作。(这里为什么要存hash,知道hash值和知道名字,安全性还是不一样,你知道怎么操作,但是不需要知道操作的本身?)
保存在/data/tee目录以数字命名的文件是被安全存储保护的用户文件。
该文件保存的是加密之后的用户数据,加密使用的密钥则是对应的FEK。
3.1 dirf.db文件和安全文件的格式
使用安全存储功能生成的文件都会使用相同的格式被保存,而且dirf.db文件与安全文件的格式也相同。
且dirf.db文件与安全文件的格式也相同。安全文件中的内容分为三个区域,分别用于保存文件头、结点、数据,文件的内容,其格式如图20-3所示。
安全文件将整个空间划分成相等大小的物理块,每个物理块的大小为4KB,其中文件头部分存放的是tee_fs_htree_image结构体的内容,该结构体定义如下:
struct tee_fs_htree_image {
//加密iv+enc_fek时使用的iv值,每次保存head时会使用随机数更新
uint8_t iv[TEE_FS_HTREE_IV_SIZE];
uint8_t tag[TEE_FS_HTREE_TAG_SIZE]; //加密iv+Enc_fek生成的数据的tag部分
uint8_t enc_fek[TEE_FS_HTREE_FEK_SIZE]; //使用TSK加密一个安全文件的fek生成的
//加密iv+Enc_fek生成的数据的imeta部分
uint8_t imeta[sizeof(struct tee_fs_htree_imeta)];
uint32_t counter; //用于计算在保存tee_fs_htree_image时是存到ver0还是ver1
};
节点部分存放的是tee_fs_htree_node_image结构体的内容,在保存数据到每个物理块之前都会使用FEK和对应的IV值对需要被保存的数据进行加密,而在打开读取文件时则会首先从文件头中读取enc_fek的值,然后使用TSK做解密操作来获取FEK,最后从需要被解密的物理块对应的节点中获取到IV值。
tee_fs_htree_node_image的结构体的定义如下:
struct tee_fs_htree_node_image {
//保存节点的哈希值,用于在操作文件时找到该文件的head
uint8_t hash[TEE_FS_HTREE_HASH_SIZE];
//加密安全文件数据区域中某一个块时使用的iv值,块数据的每次写入都会使用随机数更新
uint8_t iv[TEE_FS_HTREE_IV_SIZE];
uint8_t tag[TEE_FS_HTREE_TAG_SIZE]; //加密安全数据区域中一个块数据时生成的tag
uint16_t flags; //用于计算使用块中的那个ver
};
数据块中保存的是密文数据,该密文数据是使用该文件对应的FEK和块对应的IV值对需要被保存的数据进行加密操作来生成。
dirf.db文件的数据块区域保存的是所有使用安全存储功能保存的文件的相关信息,在安全存储功能中使用dirfile_entry结构体来表示每个安全文件的基本信息,该结构体定义如下:
struct dirfile_entry {
TEE_UUID uuid; //创建该安全文件的TA的UUID
uint8_t oid[TEE_OBJECT_ID_MAX_LEN]; //安全文件的名字(使用安全存储操作时的名字)
uint32_t oidlen; //文件名字的长度
//data/tee目录下安全文件的root node的哈希值
uint8_t hash[TEE_FS_HTREE_HASH_SIZE];
uint32_t file_number; //保存在/data/tee目录下的文件编号
};
3.2 安全存储功能中使用的重要结构体
在整个安全存储功能的操作过程中,存在一些很重要的结构体,这些结构体用于记录或保存所有安全文件和dirf.db文件的操作信息,这些结构体的关系框图如图20-4所示。
□ tee_fs_htree_node_image:用于保存文件的节点信息,通过节点可找到对应文件的头部或数据块信息;
□ tee_fs_htree_image:用于保存安全文件的头部数据,从头部数据中可获取安全文件的加密密钥和加密头部时使用的IV值;
□ tee_fs_fd:安全存储操作时使用的重要结构体,存放对文件操作时使用的fd、dir、TA的UUID等信息。
3.3 安全存储中的文件节点组成
在安全存储中,dirf.db文件和安全文件都是使用二叉树的方式来保存文件编号或数据块。
dirf.db文件的数据块区域保存的是dirfile_entry结构体变量(密文保存), dirf.db文件中的节点区域保存的是与保存的数据块相对应的节点信息。通过查找dirf.db文件中的tee_fs_htree_node_image就能找到对应的dirfile_entry数据块的数据。
在安全文件中同样也存在这样的对应关系,只不过数据块中保存的不再是dirfile_entry,而是实际需要被保存的数据。
二叉树的保存方式如图20-5所示,第一个节点作为dirf.db文件或安全文件的根节点使用。
3.4 查询安全文件中的特定数据块
使用安全存储对已经保存的安全文件执行读写等操作时,都会先打开dirf.db文件,读取dirf.db文件中的数据区域。获取到安全存储中保存的所有文件的dirfile_entry信息,然后对比dirfile_entry中uuid和obj_id与需要被操作的安全文件的uuid和obj_id是否匹配,如果匹配则获取对应的文件编号。该文件编号就是保存在/data/tee目录下需要被操作的安全文件。
查询到安全文件的文件编号后,通过计算需要读取的数据在安全文件中的位置来确定块编号,然后通过该块对应的节点ID获得该块的IV值,使用保存在安全文件头中的FEK和获得的块的IV值对块内容进行加/解密操作。最后将处理后的数据写入块中或返回给用户。整个过程的大致流程如图20-6所示。
整个操作过程中,节点ID与块编号的对应关系是:节点ID =块编号+ 1,而选取的是块中的哪个ver则与节点ID的ver值相同。
在前辈的书里面我们知道关于安全内存的东西,下一步来看看怎么用?
版权归原作者 Hkcoco 所有, 如有侵权,请联系我们删除。