Learning notes on TA's signature, signature verification, loading and calling

Posted by llanitedave on Wed, 15 Dec 2021 13:07:23 +0100

Signature of TA

Take optee OS version 3.11 as an example. In optee_ The OS directory stores the private key and signature script of the signature.
Project directory / optee_os/keys/default_ta.pem
Project directory / optee_os/scripts/sign_encrypt.py
Compiling optee OS compiles the ta into an elf file. At this point, execute the signature script to sign and generate the elf file ta file. The script also places the header data, shdr, on the header of the ta image. The magic number is placed in the header, which needs to match the magic number in the optee OS code. The rest is signature related data.

/* struct shdr - signed header*/
struct shdr {
	uint32_t magic;
	uint32_t img_type;
	uint32_t image_size;
	uint32_t algo;
	uint16_t hash_size;
	uint16_t sig_size;
};

The following figure shows the contents of the signed TA image file.

TA's signature

Compiling optee_os, PEM_ to_ pub_ c. Default in py script_ ta. Parsing der format RSA in PEM_ Pub public key, put it in ta_pub_key.c. data of documents_ pub_ key_ In the module array, it means that the public key is placed in the rodata section. This array shdr when optee OS loads TA_ verify_ The signature function will obtain the public key from this array for signature verification. During signature verification, only the head shdr is verified, and the tee on the ree side is called through rpc_ Supplicant, loading the header shdr of TA at first, then calling shdr_. verify_ Signature reads the signature information of the public key and shdr for signature verification.

/* Array of public keys */
const unit8_t ta_pub_key_modulus[];
/* Validate header signature */
res = shdr_verify_signature(shdr);

Loading of TA

Dynamic TA loading

The commands (opensession \ invoke \ closure) called by GP standard to communicate with TA are actually STD smc calls. After the smc call, it will enter TEE in TEE_ entry_ In STD.
In Tee_ entry_ The call in the STD function is the interface of opensession\invoke\closession. Call to entry_open_session interface. The dynamic TA will eventually be in tee_ ta_ init_ user_ ta_ Load in the session function.

ldelf

tee_ ta_ init_ user_ ta_ Call load in session_ Ldelf function. The implementation is roughly to compile ldelf in the ldelf directory under optee OS during code compilation Elf binary, through Gen_ ldelf_ hex. The PY script generates the binary file into ldelf_hex.c documents. Its binary data is written to the array ldelf_data, and the code segment, the size of the data segment. In load_ The ldelf function loads it into memory. Then through init_ with_ The ldelf function calls thread_ enter_ user_ The mode function switches to the user mode el0 and runs the code of this ldelf, which is used to load the TA to be loaded. Call ldelf → ta_elf_load_main → load_main loads the binary file of TA, calls the optee kernel through the system, and then switches the kernel to system PTA, this TA loads the image of the dynamic TA. invoke this TA and call system_open_ta_binary. This function traverses the registered TA_STORE, call open and other operation functions.
The following two are registered in the optee OS version of 3.11.

TEE_TA_REGISTER_TA_STORE(9) = {
	.description = "REE",
	.open = ree_fs_ta_open,
	.get_size = ree_fs_ta_get_size,
	.get_tag = ree_fs_ta_get_tag,
	.read = ree_fs_ta_read,
	.close = ree_fs_ta_close,
};
TEE_TA_REGISTER_TA_STORE(4) = {
	.description = "Secure Storage TA",
	.open = secstor_ta_open,
	.get_size = secstor_ta_get_size,
	.get_tag = secstor_ta_get_tag,
	.read = secstor_ta_read,
	.close = secstor_ta_close,
};

ree_fs_ta_open function parsing:
Call rpc first_ Load: call tee on ree side through rpc_ Supplicant, pass in UUID, and the ree side loads the TA binary file corresponding to UUID into the shared memory in rpc_load contains rpc requests for TA loading twice. The first parameter contains only UUID, because optee OS does not know the image size of the loaded TA at this time. After receiving the TA loading request, tee supplicant parses the image size of the parameter as 0, so it reads the TA file through UUID and calculates the ta_size, it is found that the incoming size is insufficient, so the TA is not loaded at this time, and the size that needs to be passed in is returned to the rpc of optee OS_ Load(), after obtaining the size, request the shared memory of size through rpc and obtain the address. At this time, the UUID, shared memory address and size are passed in as the second rpc load TA request. After receiving it, tee assistant loads the TA into the corresponding shared memory.

/* Request TA from tee-supplicant */
res = rpc_load(uuid, &ta, &ta_size, &mobj);

Then call shdr_alloc_and_copy: via RPC_ The shared memory address loaded by the TA obtained after load() because the TA image adds the signature related header file shdr during signature. In this function, calculate the size of shdr, struct shdr + hash_size + sig_size. Then apply for this size of memory and copy the shdr of TA image to the applied memory. Used for the subsequent signature verification process.

/* Make secure copy of signed header */
shdr = shdr_alloc_and_copy(ta, ta_size);

Then call shdr_verify_signature performs signature verification. Pass in the shdr pointer to the image header file. Shdr includes magic and img_type,img_size, algo and other parameters. First, check whether magic in the code is consistent with that in shdr, starting from Ta mentioned earlier_ pub_key. Read TA in C_ pub_key_ Modulus, extract pub_key, extract hash and sig from shdr, and use rsa algorithm to verify the shdr.

/* Validate header signature */
res = shdr_verify_signature(shdr);

Then in REE_ fs_ ta_ In open(), the uuid in the TA image should be taken out and compared with the incoming uuid, and the hash should be initialized_ ctx.
When ldelf completes, it returns to the optee OS kernel.

Call of TA

Call of dynamic TA

Go back to tee from the previous ldelf_ ta_ open_ Session function. User_ is called in this function. TA registered entry_ open_ session.

res = ctx->ops->enter_open_session(s, param, err);
static const struct tee_ta_ops user_ta_ops __rodata_unpaged = {
	.enter_open_session = user_ta_enter_open_session,
	.enter_invoke_cmd = user_ta_enter_invoke_cmd,
	.enter_close_session = user_ta_enter_close_session,
	.dump_state = user_ta_dump_state,
#ifdef CFG_FTRACE_SUPPORT
	.dump_ftrace = user_ta_dump_ftrace,
#endif
	.destroy = user_ta_ctx_destroy,
	.get_instance_id = user_ta_get_instance_id,
	.handle_svc = user_ta_handle_svc,
};

And whether it's open_ session,close_session,invoke_cmd and other CA calls are made through user in the optee OS kernel_ ta_ Handled by the enter function. Call thread_ in this function enter_ user_ Switch mode to el0 and execute TA. TA inlet is__ ta_entry: select different execution functions (open, close and invoke) according to different calling commands. Here is open_session. Enter the entry_open_session, and finally execute to the GP interface of TA_OpenSessionEntryPoint.

Call of static TA

Static TA, or PTA, is compiled into the kernel as part of the optee kernel. It does not need to be loaded. The open/invoke/close called by CA is the same as the dynamic TA. Different from the dynamic TA, the actual corresponding to calling the dynamic TA is user_ta_ops registered operation function, which is mentioned above. The interface corresponding to calling the static TA becomes the following pseudo_ta_ops interface, corresponding to these interfaces registered with PTA, open_session_entry_point,invoke_command_entry_point et al. Therefore, the calling process of Ca is the same.

static const struct tee_ta_ops pseudo_ta_ops = {
	.enter_open_session = pseudo_ta_enter_open_session,
	.entry_invoke_cmd = pseudo_ta_enter_invoke_cmd,
	.enter_close_session = pseudo_ta_enter_close_session,
	.destroy = pseudo_ta_destroy,
};
pseudo_ta_register(.uuid = PTA_INVOKE_TEST_UUID, .name = TA_NAME,
		   .flag = PTA_DEFAULT_FLAGS | TA_FLAG_SECURE_DATA_PATH |
				   TA_FLAG_CONCURRENT | TA_FLAG_DEVICE_ENUM,
		   .create_entry_point = create_ta,
		   .destroy_entry_point = destroy_ta,
		   .open_session_entry_point = open_session,
		   .close_session_entry_point = close_session,
		   .invoke_command_entry_point = invoke_command);

Topics: Linux security