技术标签: SFS 安全存储文件结构 TEE RPMB 安全存储 OPTEE
我们知道TEEOS最重要的功能莫过于安全存储了,这是一切安全的前提,根据存储安全性和使用场景GP TEE安全存储分为RPMB安全存储、SFS安全存储和SQLFS安全存储。如下图所示,临时对象、持久化对象、持久化枚举、数据流,分别可以存储在RPMB、SFS、SQLFS三种存储媒介上。
RPMB(Replay Protected Memory Block),作用在于存放机密数据。由于访问需要密钥,所以可以防止未授权的访问并且在每次的数据写入时都需要验证Write Counter寄存器值,这个寄存器值每写入成功便会加1,如果是黑客截取写入报文再进行重放攻击,由于counter已经更新了写入会无效。系统开机时在preloader阶段写入RPMB鉴权密钥,为保证密钥的安全性和保密密钥的分散方法,密钥一般由TEEOS提供库接口获取,在preloader跳入ATF执行时把密钥以参数传递方式放入安全SRAM,在TEEOS启动再将该密钥拷贝到安全DRAM中用于后续的RPMB操作。RPMB特点是非安全世界不可见,使用场景是安全性要求高、容量小,可以有效防止回滚和重放攻击。
SFS(File Storage Service)文件存储服务。一个SFS安全存储对象在Linux/Android端会生成多个文件,数据块文件和对应的meta文件,SFS特点是非安全世界可见,无法有效防止回滚,但可以同时在SFS和RPMB中写入读写次数检测回滚。使用场景是安全性要求略低、容量大。
SQLFS机制与SFS类似,由Android侧提供SQLITE数据库操作接口,一个安全存储对象在Linux/Android下只会生成单个文件,文件会比SFS少,并且SQLITE本身会支持到数据库的原子性操作和容错机制。特点是非安全世界可见,使用场景是安全性要求略低、容量大。
OP-TEE中的安全存储是根据 GlobalPlatform 的 TEE 内部核心 API(这里称为可信存储)中定义的内容来实现的。本规范规定,应该可以存储通用数据和关键材料,以保证所存储数据的机密性和完整性以及修改存储的操作的原子性(这里的原子性意味着整个操作要么成功完成,要么不写)。
目前,OP-TEE中有两种安全存储实现:
以下是规范摘录,列出了最重要的要求:
- 只要应用了适当的加密保护,可信存储就可以由非安全资源支持,而加密保护必须与保护TEE代码和数据本身的方法一样强大。
- 可信存储必须绑定到特定设备,这意味着数据被创建时只有在同一TEE和同一设备上运行的授权ta才能访问或修改它
- TA本身能够隐藏敏感关键数据
- 每个TA都可以访问其自己的存储空间,该存储空间在该TA的所有实例之间共享,但与其他TA分离。
- 受信任存储必须提供最低级别的保护,以防回滚攻击。可以接受的是,实际的物理存储可能位于不安全的区域,因此容易受到来自TEE外部的操作的影响。通常,一个实现可以依赖于REE来达到这个目的(保护级别100),或者依赖于TEE控制的硬件资产(保护级别1000)。
(see GP TEE Internal Core API section 2.5 and 5.2)
默认情况下,OP-TEE 使用 /data/tee/
作为 Linux 文件系统中的安全存储空间。每个持久化对象都分配了一个内部标识符。它是一个整数,在 Linux 文件系统中显示为 /data/tee/<file number>
。
目录文件 /data/tee/dirf.db
,列出安全存储中的所有对象。所有正常世界的文件都是完整性保护和加密的。
密钥管理器是 TEE 文件系统中的一个组件,负责处理数据的加密和解密以及对敏感密钥的管理。密钥管理器使用三种类型的密钥:安全存储密钥(Secure Storage Key, SSK)
、TA存储密钥(Trusted Application Storage Key, TSK)
和文件加密密钥(File Encryption Key, FEK)
。
(Hardware Unique Key, HUK)
大多数设备都有某种硬件唯一密钥(HUK
),HUK
的重要之处在于它需要得到很好的保护,并且在最好的情况下,HUK
永远不应该直接从软件读取,甚至不应该从安全方面读取。一般存储在硬件Fuse介质中,熔断后无法修改,软件一般无法直接读取到,一般设计由硬件加速器访问,主要用于派生其他密钥。例如,当派生密钥用于安全存储等时,可以使用 HUK
派生。
在 OP-TEE 中,HUK
只是一个存根,您将看到在core/include/kernel/tee_common_otp.h
中名为 tee_otp_get_hw_unique_key
的函数中。
struct tee_hw_unique_key {
uint8_t data[HW_UNIQUE_KEY_LENGTH];
};
TEE_Result tee_otp_get_hw_unique_key(struct tee_hw_unique_key *hwkey);
在真正安全的产品中,必须用其他东西替换它。如果你的设备缺少对HUK的硬件支持,那么你至少必须将其更改为除零之外的其他内容。但是,请记住,在软件中存储密钥不是一个好的安全做法,尤其是不能将密钥作为其他所有内容的根,因此我们建议您不要这样做。
(Secure Storage Key, SSK)
SSK
是每个设备的密钥,在OP-TEE
启动时生成并存储在安全内存中。SSK
用于派生TA存储密钥(TSK
)。
SSK = HMACSHA256 (HUK, Chip ID || “static string”)
获取硬件唯一密钥(HUK
)和芯片ID
的功能取决于平台实现。目前,OP-TEE 系统中每台设备只有一把 SSK
,用于安全存储子系统。但是,为了将来,我们可能需要为每台设备使用生成 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)];
/* Secure Storage Key Generation:
*
* SSK = HMAC(HUK, message)
* message := concatenate(chip_id, static string)
* */
/* 获取HUK的值(该接口的实现与平台有关,不同的芯片具有不同的读取HUK值的方式) */
tee_otp_get_hw_unique_key(&huk);
/* 获取chip ID的值(不同的芯片具有不同的读取chip id值的方式)*/
tee_otp_get_die_id(chip_id, sizeof(chip_id));
/* 将chip id + string_for_ssk_gen连接后的值保存到message中,string_for_ssk_gen是一个
* 静态的字符串,该值被hard code在代码中
*/
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;
}
(Trusted Application Storage Key, TSK)
TSK
是每个受信任的应用程序密钥,由SSK和TA的标识符(UUID)
生成。它被用来保护FEK
,换句话说,用来加密/解密FEK
。
TSK = HMACSHA256 (SSK, TA_UUID)
(File Encryption Key, FEK)
当一个新的TEE文件被创建时,密钥管理器将通过 PRNG
(pesudo随机数生成器
)或者TRNG
为TEE文件生成一个新的 FEK
,并将加密的 FEK
存储在 meta
文件中。FEK 用于对存储在 meta
文件中的TEE文件信息或块文件中的数据进行加密/解密。
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 {
/*
* Pick something of a different size than TEE_UUID to
* guarantee that there's never a conflict.
*/
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;
}
tee_fs_htree_image
区域中的数据按照meta data
方式加密生产,该加密的过程如下图所示:
上述加密操作过程中相关元素说明如下:
FEK:
安全文件和dirf.db
文件在执行加密操作时使用的key,该值在文件创建的时候使用随机数的方式生成,对已经创建好的文件进行操作时,该值会从tee_fs_htree_image
中的enc_fek
成员中使用TSK
解密获得。
TSK:
TA Applicant storage key
, 使用ssk
和UUID
执行HMAC
计算得到
AES_ECB
: 将FEK
使用TSK
进行AES
的ECB
模式进行加密操作生成enc_fek
Encrypted FEK
: 使用TSK
加密FEK
得到,保存在tee_fs_htree_image
的enc_fek
中,最终会被写入到安全文件或者dirf.db
文件头的head
中
Meta IV
: 使用secure storage
创建文件或者将tee_fs_htree_image
写入到文件中是都会被随机生成,最终会被写入到安全文件或者dirf.db
文件头的head
中
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
成员中
data block
和tee_fs_htree_node_image
中的数据按照block data encryption
的方式加密数据块生成,block data encryption
方式的加密过程如下:
上述加密操作过程中相关元素说明如下:
Encrypted FEK
: 使用TSK
加密FEK
得到,保存在tee_fs_htree_image
的enc_fek
中,最终会被写入到安全文件或者dirf.db
文件头的head
中
TSK
: TA Applicant storage key
, 使用ssk
和UUID
执行HMAC
计算得到
AES_ECB
: 将Encrypted FEK
使用TSK
进行AES
的ECB
模式进行解密操作生成FEK
FEK
: 解密Encrypted FEK
之后生成的FEK
,用于加密需要被保存的数据block
Block IV
: 每次加密数据区域中每个block
是都会随机生成,然后被保存到tee_fs_htree_node_image
变量的iv
成员中
Block Data
: 将需要被保存的数据更新到对应的block
的数据的正确位置之后生成的新的block的完成明文数据
AES_GCM
: 将Block IV+Block data
使用FEK
和Block IV
进行AES
的GCM
模式进行加密操作生成Tag
和Encryption Block Data
数据
Tag
: 加密Block IV+Block data
时生成的tag
值,数据会被保存在tee_fs_htree_node_image
中的tag
成员中
Encryption Block Data
: 加密Block IV+Block data
时生成的Encryption Block Data
值,数据会被保存在文件中的数据区域对应的block
中。
使用安全存储功能生成的文件都会使用相同的格式被保存,而且dirf.db
文件与安全文件的格式也相同。
安全文件中的内容分为三个区域,分别用于保存文件头、结点、数据,文件的内容。
OPTEE使用哈希树结构负责处理安全存储文件的数据加密和解密。哈希树被实现为二叉树,树中的每个节点(struct tee_fs_htree_node_image
)保护其两个子节点和一个数据块。元数据(meta data
)存储在一个头(struct tee_fs_htree_image
)中,它也保护顶层节点。
所有字段(头、节点和块)都使用0
和1
两个版本进行复制,以确保原子更新。有关详细信息,请参见core/tee/fs_htree.c。
tee_fs_htree_node_image
:用于保存文件的节点node信息,通过节点可找到对应文件的头部或数据块信息,包含hash、iv、tag
以及flags
。tee_fs_htree_image
:用于保存安全文件的头部数据,从头部数据中可获取安全文件的加密密钥和加密头部时使用的IV值。tee_fs_fd
:安全存储操作时使用的重要结构体,存放对文件操作时使用的fd、dir、TA
的UUID
等信息。Phys block
的大小为4K
结构体定义如下:
#define TEE_FS_HTREE_HASH_SIZE TEE_SHA256_HASH_SIZE
#define TEE_FS_HTREE_IV_SIZE U(16)
#define TEE_FS_HTREE_FEK_SIZE U(16)
#define TEE_FS_HTREE_TAG_SIZE U(16)
/* Internal struct provided to let the rpc callbacks know the size if needed */
struct tee_fs_htree_node_image {
/* Note that calc_node_hash() depends on hash first in struct */
uint8_t hash[TEE_FS_HTREE_HASH_SIZE]; /* 保存节点的hash值,用于在操作文件的时候找到该文件的head */
uint8_t iv[TEE_FS_HTREE_IV_SIZE]; /* 加密安全文件数据区域中某一个block时使用的iv值,block数据的每次写入都会使用随机数更新 */
uint8_t tag[TEE_FS_HTREE_TAG_SIZE]; /* 加密安全数据区域中够一个block数据时生成的ta */
uint16_t flags; /* 用于计算使用block中的那个ver */
};
/* Internal struct provided to let the rpc callbacks know the size if needed */
struct tee_fs_htree_image {
uint8_t iv[TEE_FS_HTREE_IV_SIZE]; /* 加密iv+enc_fek时使用的iv值,每次保存head时会使用随机数更新 */
uint8_t tag[TEE_FS_HTREE_TAG_SIZE]; /* 加密iv+Enc_fek生成的数据的tag部分 */
uint8_t enc_fek[TEE_FS_HTREE_FEK_SIZE]; /* 使用TSK加密一个安全文件的fek生成的 */
uint8_t imeta[sizeof(struct tee_fs_htree_imeta)]; /* 加密iv+Enc_fek生成的数据的imeta部分 */
uint32_t counter; /* 用于计算在保存tee_fs_htree_image的时候是存到ver0还是ver1 */
};
struct tee_fs_fd {
struct tee_fs_htree *ht; /* 哈希树头 */
int fd; /* 文件句柄 */
struct tee_fs_dirfile_fileh dfh; /* dirfile_entry结构体来表示每个安全文件的基本信息 */
const TEE_UUID *uuid; /* TA UUID */
};
这些重要的结构体的关系框图如下:
补充剩余几个结构体描述:
tee_fs_htree_storage
结构体定义如下,包含了读写RPC
调用的函数指针。/**
* struct tee_fs_htree_storage - storage description supplied by user of
* this interface
* @block_size: size of data blocks
* @rpc_read_init: initialize a struct tee_fs_rpc_operation for an RPC read
* operation
* @rpc_write_init: initialize a struct tee_fs_rpc_operation for an RPC
* write operation
*
* The @idx arguments starts counting from 0. The @vers arguments are either
* 0 or 1. The @data arguments is a pointer to a buffer in non-secure shared
* memory where the encrypted data is stored.
*/
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);
};
tee_fs_htree_meta
元数据长度/*
* This struct is not interpreted by the hash tree,
* it's up to the user of
* the interface to update etc if needed.
*/
struct tee_fs_htree_meta {
uint64_t length;
};
tee_fs_dirfile_dirh
表示每个安全文件的基本信息,包含文件读写函数指针,/**
* struct tee_fs_dirfile_operations - file interface supplied by user of this
* interface
* @open: opens a file
* @close: closes a file, changes are discarded unless
* @commit_writes is called before
* @read: reads from an open file
* @write: writes to an open file
* @commit_writes: commits changes since the file was opened
*/
struct tee_fs_dirfile_operations {
TEE_Result (*open)(bool create, uint8_t *hash, const TEE_UUID *uuid,
struct tee_fs_dirfile_fileh *dfh,
struct tee_file_handle **fh);
void (*close)(struct tee_file_handle *fh);
TEE_Result (*read)(struct tee_file_handle *fh, size_t pos,
void *buf, size_t *len);
TEE_Result (*write)(struct tee_file_handle *fh, size_t pos,
const void *buf, size_t len);
TEE_Result (*commit_writes)(struct tee_file_handle *fh, uint8_t *hash);
};
struct tee_fs_dirfile_dirh {
const struct tee_fs_dirfile_operations *fops; /* 文件操作函数指针 */
struct tee_file_handle *fh;
int nbits;
bitstr_t *files;
size_t ndents;
};
参考:
奔人之旅的文章-【OP-TEE】安全存储
OP-TEE中secure stroage------安全文件数据格式和操作过程
问题 B: NOIP2015 斗地主时间限制:3 Sec内存限制:1024 MB题目描述牛牛最近迷上了一种叫斗地主的扑克游戏。斗地主是一种使用黑桃、红心、梅花、方片的A到K加上大小王的共54张牌来进行的扑克牌游戏。在斗地主中,牌的大小关系根据牌的数码表示如下:3<4<5<6<7<8<9<10<J<Q<...
* @Scope:调整作用域 * prototype:多实例的:ioc容器启动并不会去调用方法创建对象放在容器中。 * 每次获取的时候才会调用方法创建对象; * singleton:单实例的(默认值):ioc容器启动会调用方法创建对象放到ioc容器中。 * 以后每次获取就是直接从容器(map.get())中拿, * request:同一次请求创建一个实例 * session:同一个session创建一个实例** 懒...
测试服务器配制 os : window2003 oracle 9I memery : 8G cpu intel 8 core arc
[相关Blog文章][转贴]游戏引擎演化史 2005-05-06 magus_yang 可移植、可扩展多人3D游戏引擎的设计与构架 2005-02-03 zhudelunalpha 由开发卡片游戏想到的 2006-05-14 coollen_mmx 游戏引擎 2004-08-07 gullsky 3D体素引擎与Ken Silverman 2006-04-08 w
我是今年7月将要毕业的女“烟酒僧”(意为研究生),作为命途多舛的一届,高考遇上了非典,毕业遇到了就业危机。无奈何,家世平淡的我只好从2008年9月开始,孤身一人闯荡“求职”江湖,搞得面如土色,满脸痘痘,横冲直撞总算搞定了 14个offer。我有一句大话,“过了笔试,面试我是不怕的”,理由如下,第一、我学历还算优秀,“基础设施”比较好;第二、我的实践经历相对同龄人较为丰富;第三、性格的多面性让...
参考:https://en.wikipedia.org/wiki/Wi-Fi_Protected_Access 1. wpaWi-Fi Protected Access (WPA), Wi-Fi Protected Access II (WPA2), and Wi-Fi Protected Access 3 (WPA3) are three security protocols and...
为什么80%的码农都做不了架构师?>>> ...
public bool IsVisible(Bounds bounds, Camera camera) { // 得到摄像机的六个面 Plane[] planes = GeometryUtility.CalculateFrustumPlanes(camera); // 判断边框bound是否在六个面内 return GeometryUtility.TestPlanesAABB(planes, bo
GLSL. 语法基础
管理聚合链路和桥接网络一、配置Bonding接口,实现多网卡绑定绑定多网卡,可以有以下优点:提升网络传输能力避免网络链路单点故障Bonding的两种绑定工作模式:实际上有7种,其他不常用0 balance-rr 轮询模式1 active-backup 高可用模式逻辑原理--> eth0 ----\app --数据发送到--> ...
python中容易被忽略的小知识点:python中占位符:在输出的地方预定的符号1.%d 整数占位符%d只能占位整数,即使是原数字为浮点数他也会强制转换变成整数。2.%f 浮点数占位符%f只能占位浮点数,%.xf 则是精确至小数点后x位。3.%s 字符串占位符%s占位字符串,应用最多。age = int(input("输入年龄:"))work = inpu...
公司写的网站要英文和中文的,或者门户网站需要移动端和PC端两种布局,所以就写了两个项目,都是用vue写的单页面项目,但是域名只有一个,所以就想把两个vue项目合并到一个域名下面。实现起来很简单:1.配置基本路径:* 第一个项目在vue.config.js 中 配置 publicPath: "/first/"* 第二个项目在vue.config.js 中 配置 publicPath: "/second/"2.服务器上分别新建两个项目文件夹3.后台nginx配置:location ~ /(\w