TA的加密
TA在被编译的时候会在optee_os/ta/arch/arm/link.mk里进行一些操作,包括调用脚本对编译出的elf文件进行签名加密生成.ta。
而签名加密的核心在于调用optee_os/scripts/sign_encrypt.py脚本。采用RSA签名,AES加密。当CFG_ENCRYPT_TA变量为y时,TA加密功能被打开。
TA加密功能打开后,在optee开源代码中存放的加密密钥是默认的上图中的TA_ENC_KEY,这个密钥只是作为演示功能,用在自己的工程中替换为自己的密钥。在调用签名加密脚本时候,传入签名私钥,加密的密钥。
目前的从git下载的optee代码中没有打开TA加密功能,只有TA签名功能。如何增加TA的加密功能,以optee_examples工程下的hello_world TA为例。我将CFG_ENCRYPT_TA变量定义在makefile里。CFG_ENCRYPT_TA ?= y
编译时就会调用脚本对hello_world的TA进行了AES加密。加密后其镜像头部会增加ehdr的数据。存放在shdr_encrypte_ta类型的结构体里。
struct shdr_encrypte_ta {
uint32_t enc_algo;
uint32_t flags;
uint16_t iv_size;
uint16_t tag_size;
};
签名加密后的TA文件变成了 uuid.ta,比如hello_world编译签名加密后变成了8aaaf200-2450-11e4-abe2-0002a5d5c51b.ta,其通过sig_encrypto.py脚本添加的头部信息如上图。
shdr是与签名有关的信息。
magic:需要与optee-os里的匹配。
img_type:TA镜像类型,是否加密。
img_siez:TA镜像大小。
alg:签名算法,默认RSA。
hash_size::TA签名的摘要大小。此摘要是除去sig和hash后对整个ta文件的摘要
sig_size:TA签名的大小。签名是对前面摘要的签名。
bs_hdr是和TA相关的信息
uuid:此TA的uuid.
ta_version:TA的版本号
ehdr是和加密相关的信息
enc_alg:加密算法,默认是AES。
flag:加密的标识,代表密钥类型。
iv_size:加密iv值的大小。
tag_size:tag的大小。
TA的解密
加密功能使能的TA其头部shdr会存放签名相关的信息。shdr里的img_type变量存放TA镜像类别,是否加密。optee-os加载TA时会判断其是否加密,没有加密则只验签,加密则先验签和解密。
解密TA依赖于 tee_ta_decrypt_init函数。其调用tee_otp_get_ta_enc_key函数获取TA加密的密钥用于解密。此函数在optee里默认从固定的数组(也就是optee-os里保存的用于验签TA的公钥)里派生。用于自己的工程需要修改为自己获取密钥的方式。
那么默认的密钥是如何派生的呢?
通过huk_subkey_derive函数,将用于派生的数组传入函数,进行HAMC_SHA256计算,得到digest就是派生出的密钥,此密钥和link.mk里的一样,不一样则TA解密失败。此处可以替换为自己的密钥获取方式,但是需要和link.mk里的一致。
const uint8_t ta_pub_key_modulus[] = {
0xa6, 0x5a, 0x18, 0xad, 0xc0, 0xb3, 0xce, 0xe3,
....
};
得到密钥后调用
res = crypto_authenc_init(*enc_ctx, TEE_MODE_DECRYPT, key, sizeof(key),
SHDR_ENC_GET_IV(ehdr), ehdr->iv_size,
ehdr->tag_size, 0, len);
传入密钥,初始化解密上下文。
在后面的TA已经加载完成后,ree_fs_ta_read时进行解密的工作。因为TA是分段加载的,所以解密也是分段的,需要多次解密。
static TEE_Result ree_fs_ta_read(struct user_ta_store_handle *h, void *data,
size_t len)
{
struct ree_fs_ta_handle *handle = (struct ree_fs_ta_handle *)h;
/*获取TA镜像除去头之后的elf格式所在位置*/
uint8_t *src = (uint8_t *)handle->nw_ta + handle->offs;
size_t next_offs = 0;
uint8_t *dst = src;
TEE_Result res = TEE_SUCCESS;
if (ADD_OVERFLOW(handle->offs, len, &next_offs) ||
next_offs > handle->nw_ta_size)
return TEE_ERROR_BAD_PARAMETERS;
/*判断TA类型*/
if (handle->shdr->img_type == SHDR_ENCRYPTED_TA) {
if (data) {
dst = data; /* Hash secure buffer */
/*解密len长度的数据到安全内存中,加载多长的数据就解密多长的数据*/
res = tee_ta_decrypt_update(handle->enc_ctx, dst, src,
len);
if (res != TEE_SUCCESS)
return TEE_ERROR_SECURITY;
} else {
size_t num_bytes = 0;
size_t b_size = MIN(1024U, len);
uint8_t *b = malloc(b_size);
if (!b)
return TEE_ERROR_OUT_OF_MEMORY;
dst = NULL;
while (num_bytes < len) {
size_t n = MIN(b_size, len - num_bytes);
res = tee_ta_decrypt_update(handle->enc_ctx, b,
src + num_bytes, n);
if (res)
break;
num_bytes += n;
res = crypto_hash_update(handle->hash_ctx, b,
n);
if (res)
break;
}
free(b);
if (res != TEE_SUCCESS)
return TEE_ERROR_SECURITY;
}
} else if (data) { /*不是加密的TA则直接拷贝*/
dst = data; /* Hash secure buffer (shm might be modified) */
memcpy(dst, src, len);
}
if (dst) {
/*更新hash_ctx,以便后面计算出TA的hash值使用*/
res = crypto_hash_update(handle->hash_ctx, dst, len);
if (res != TEE_SUCCESS)
return TEE_ERROR_SECURITY;
}
/*将句柄的偏移更新*/
handle->offs = next_offs;
if (handle->offs == handle->nw_ta_size) {/*判断当前是否已经加载了所有的segment*/
if (handle->shdr->img_type == SHDR_ENCRYPTED_TA) {
/*
* Last read: time to finalize authenticated
* decryption.
*/
/*加载所有segment之后进行ctx final收尾工作*/
res = tee_ta_decrypt_final(handle->enc_ctx,
handle->ehdr, NULL, NULL, 0);
if (res != TEE_SUCCESS)
return TEE_ERROR_SECURITY;
}
/*
* Last read: time to check if our digest matches the expected
* one (from the signed header)
*/
res = check_digest(handle);/*加载所有segment之后进行hash校验与shdr里的比较*/
if (res != TEE_SUCCESS)
return res;
if (handle->bs_hdr)
res = check_update_version(handle->bs_hdr);
}
return res;
}
镜像的解密是随着TA的elf文件的加载进行的,TA的elf是分段多次加载,先加载elf head,所以就先解密elf head,所以TA的elf每次加载都会触发解密操作。直到TA elf文件完全加载完成。