From 163caa0abd98fd02b9c0ecb62ecb9f3d1881114c Mon Sep 17 00:00:00 2001 From: Tuomas Salokanto Date: Thu, 20 Nov 2025 11:03:03 +0200 Subject: [PATCH] ta: Add Application Secrets TA MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Application Secrets TA is a general purpose secret protector that provides an API for sealing secrets. Sealing refers to encrypting the client-provided confidential payload and protecting the integrity of the TA-specific header inserted to sealed blobs. AES-256-GCM is used as the crypto algorithm as it provides authenticated encryption, i.e. strong encryption for the secret payload and integrity protection for the header at the same time. The key is derived from HUK in order to base the security on hardware. This is also what the trusted keys TA does. IV is (only) 12 bytes (a sufficiently good random number generator is assumed) as 96 bits is the recommended length (vs. 16 in the trusted keys TA): For IVs, it is recommended that implementations restrict support to the length of 96 bits, to promote interoperability, efficiency, and simplicity of design [1]. The blob includes magic and version fields to facilitate easy detection of the format and compatibility. These are included from the very beginning for forward compatibility even though the implementation currently only supports a single blob type and version. The magic and version fields are included in the AAD for integrity protection but IV and tag are not as they are implicitly verified by AES-GCM (i.e. changing them would fail decryption / integrity verification). The blob also includes identity of the client that sealed the blob, i.e. the TEE login type and client UUID. These are compared by the unseal operation to those of the calling client in order to decide whether the client is one that sealed the provided blob and thus authorized to unseal it. The login type and the client UUID are stored in the encrypted payload in order to avoid exposing the credentials unnecessarily in plain text to anybody who might have access to the data e.g. at rest. This adds an extra layer of security even though the login type and client UUID are not necessarily secret (for login type only few options exist in the first place, and UUID is UUIDv5 based client UID or GID). The unseal operation compares the client identity only after decrypting the blob so that the integrity of the client identity stored in the blob is verified before the authorization. Inputs are first copied into locally allocated (non-shared) buffers before any processing and results to the shared output buffers only after all processing has been completed in order to isolate the TA and its internal processing from clients, and to allow clients only see final, complete results. The implementation borrows ideas from the pre-existing trusted_keys TA, but is a separate TA and a new implementation as the trusted_keys TA is purpose-built for kernel usage, and cannot utilized as is since considerable changes would be needed to accommodate userspace clients. [1] https://doi.org/10.6028/NIST.SP.800-38D Co-developed-by: Katariina Lounento Signed-off-by: Katariina Lounento Co-developed-by: Vesa Jääskeläinen Signed-off-by: Vesa Jääskeläinen Signed-off-by: Tuomas Salokanto Acked-by: Jerome Forissier --- ta/app_secrets/Makefile | 9 + ta/app_secrets/app_secrets_ta.c | 454 ++++++++++++++++++++++++ ta/app_secrets/include/app_secrets_ta.h | 29 ++ ta/app_secrets/sub.mk | 3 + ta/app_secrets/user_ta.mk | 1 + ta/app_secrets/user_ta_header_defines.h | 23 ++ 6 files changed, 519 insertions(+) create mode 100644 ta/app_secrets/Makefile create mode 100644 ta/app_secrets/app_secrets_ta.c create mode 100644 ta/app_secrets/include/app_secrets_ta.h create mode 100644 ta/app_secrets/sub.mk create mode 100644 ta/app_secrets/user_ta.mk create mode 100644 ta/app_secrets/user_ta_header_defines.h diff --git a/ta/app_secrets/Makefile b/ta/app_secrets/Makefile new file mode 100644 index 00000000000..00a59910bcc --- /dev/null +++ b/ta/app_secrets/Makefile @@ -0,0 +1,9 @@ +# The UUID for the Trusted Application +BINARY=5ca4d9d9-dee4-47f4-977a-7eadc060e52c + +-include $(TA_DEV_KIT_DIR)/mk/ta_dev_kit.mk + +ifeq ($(wildcard $(TA_DEV_KIT_DIR)/mk/ta_dev_kit.mk), ) +clean: + @echo 'Note: $$(TA_DEV_KIT_DIR)/mk/ta_dev_kit.mk not found, cannot clean TA' +endif diff --git a/ta/app_secrets/app_secrets_ta.c b/ta/app_secrets/app_secrets_ta.c new file mode 100644 index 00000000000..93690c8127a --- /dev/null +++ b/ta/app_secrets/app_secrets_ta.c @@ -0,0 +1,454 @@ +// SPDX-License-Identifier: BSD-2-Clause +/* + * Copyright (c) 2026, Vaisala Oyj. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "user_ta_header_defines.h" + +#define IV_SIZE 12 +#define TAG_SIZE 16 +#define MAX_BUF_SIZE 4096 +#define AS_BLOB_VERSION 1 +#define AS_MAGIC 0x41534543 + +static const char sealing_op_derivation_extra[] = "sealing"; + +struct secret_blob_hdr { + uint32_t magic; + uint32_t version; + uint8_t iv[IV_SIZE]; + uint8_t tag[TAG_SIZE]; + uint8_t encrypted_payload[]; +}; + +struct plaintext_payload { + uint32_t client_login; + TEE_UUID client_uuid; + uint8_t data[]; +}; + +#define SEALING_OVERHEAD (sizeof(struct secret_blob_hdr) + \ + sizeof(struct plaintext_payload)) + +static_assert(MAX_BUF_SIZE >= SEALING_OVERHEAD, + "MAX_BUF_SIZE must be at least SEALING_OVERHEAD"); + +static TEE_Result derive_unique_key(void *key, size_t key_size, + const void *extra, size_t extra_size) +{ + static const TEE_UUID system_uuid = PTA_SYSTEM_UUID; + uint32_t param_types = TEE_PARAM_TYPES(TEE_PARAM_TYPE_MEMREF_INPUT, + TEE_PARAM_TYPE_MEMREF_OUTPUT, + TEE_PARAM_TYPE_NONE, + TEE_PARAM_TYPE_NONE); + TEE_Param params[TEE_NUM_PARAMS] = { }; + TEE_TASessionHandle sess = TEE_HANDLE_NULL; + TEE_Result res = TEE_ERROR_GENERIC; + uint32_t ret_orig = 0; + + if (extra && extra_size) { + params[0].memref.buffer = (void *)extra; + params[0].memref.size = extra_size; + } + + params[1].memref.buffer = key; + params[1].memref.size = key_size; + + res = TEE_OpenTASession(&system_uuid, TEE_TIMEOUT_INFINITE, 0, NULL, + &sess, &ret_orig); + if (res != TEE_SUCCESS) { + EMSG("Can't open session to system PTA"); + return res; + } + + res = TEE_InvokeTACommand(sess, TEE_TIMEOUT_INFINITE, + PTA_SYSTEM_DERIVE_TA_UNIQUE_KEY, + param_types, params, &ret_orig); + if (res != TEE_SUCCESS) + EMSG("Can't invoke system PTA"); + + TEE_CloseTASession(sess); + + return res; +} + +static TEE_Result huk_ae_encrypt(TEE_OperationHandle crypto_op, + const uint8_t *in, size_t in_sz, + uint8_t *out, size_t *out_sz) +{ + TEE_Result res = TEE_ERROR_GENERIC; + struct secret_blob_hdr *hdr = (struct secret_blob_hdr *)(void *)out; + struct plaintext_payload *payload = NULL; + size_t encrypted_payload_len = 0; + size_t required_sz = 0; + size_t tag_len = TAG_SIZE; + TEE_Identity id = { 0 }; + uint8_t *payload_buf = NULL; + size_t payload_buf_sz = 0; + + hdr->magic = AS_MAGIC; + hdr->version = AS_BLOB_VERSION; + + res = TEE_GetPropertyAsIdentity(TEE_PROPSET_CURRENT_CLIENT, + "gpd.client.identity", &id); + if (res != TEE_SUCCESS) { + EMSG("Failed to get client identity: 0x%08x", res); + return res; + } + + TEE_GenerateRandom(hdr->iv, IV_SIZE); + + res = TEE_AEInit(crypto_op, hdr->iv, IV_SIZE, TAG_SIZE * 8, 0, 0); + if (res) + return res; + + TEE_AEUpdateAAD(crypto_op, &hdr->magic, sizeof(hdr->magic)); + TEE_AEUpdateAAD(crypto_op, &hdr->version, sizeof(hdr->version)); + + if (ADD_OVERFLOW(sizeof(struct plaintext_payload), in_sz, + &payload_buf_sz)) + return TEE_ERROR_SECURITY; + payload_buf = TEE_Malloc(payload_buf_sz, TEE_MALLOC_FILL_ZERO); + if (!payload_buf) + return TEE_ERROR_OUT_OF_MEMORY; + + payload = (struct plaintext_payload *)(void *)payload_buf; + payload->client_login = id.login; + payload->client_uuid = id.uuid; + memcpy(payload->data, in, in_sz); + + if (ADD_OVERFLOW(sizeof(*hdr), payload_buf_sz, &required_sz)) { + res = TEE_ERROR_SECURITY; + goto out; + } + if (required_sz > *out_sz) { + *out_sz = required_sz; + res = TEE_ERROR_SHORT_BUFFER; + goto out; + } + encrypted_payload_len = payload_buf_sz; + res = TEE_AEEncryptFinal(crypto_op, payload_buf, payload_buf_sz, + hdr->encrypted_payload, &encrypted_payload_len, + hdr->tag, &tag_len); + if (res || tag_len != TAG_SIZE) { + res = TEE_ERROR_SECURITY; + goto out; + } + + if (ADD_OVERFLOW(encrypted_payload_len, sizeof(*hdr), out_sz)) + res = TEE_ERROR_SECURITY; + +out: + memzero_explicit(payload_buf, payload_buf_sz); + TEE_Free(payload_buf); + return res; +} + +static TEE_Result huk_ae_decrypt(TEE_OperationHandle crypto_op, + const uint8_t *in, size_t in_sz, + uint8_t *out, size_t *out_sz) +{ + TEE_Result res = TEE_ERROR_GENERIC; + const struct secret_blob_hdr *hdr = + (const struct secret_blob_hdr *)(const void *)in; + size_t encrypted_payload_len = 0; + + if (hdr->magic != AS_MAGIC) { + DMSG("Invalid blob magic 0x%08x", hdr->magic); + return TEE_ERROR_SECURITY; + } + + if (!hdr->version || hdr->version > AS_BLOB_VERSION) { + DMSG("Unsupported blob version %u (max supported: %u)", + hdr->version, AS_BLOB_VERSION); + return TEE_ERROR_SECURITY; + } + + if (SUB_OVERFLOW(in_sz, sizeof(*hdr), &encrypted_payload_len)) + return TEE_ERROR_SECURITY; + if (encrypted_payload_len > *out_sz) { + *out_sz = encrypted_payload_len; + return TEE_ERROR_SHORT_BUFFER; + } + + res = TEE_AEInit(crypto_op, hdr->iv, IV_SIZE, TAG_SIZE * 8, 0, 0); + if (res) + return res; + + TEE_AEUpdateAAD(crypto_op, &hdr->magic, sizeof(hdr->magic)); + TEE_AEUpdateAAD(crypto_op, &hdr->version, sizeof(hdr->version)); + + res = TEE_AEDecryptFinal(crypto_op, hdr->encrypted_payload, + encrypted_payload_len, out, out_sz, + (void *)hdr->tag, TAG_SIZE); + if (res) + res = TEE_ERROR_SECURITY; + + return res; +} + +static TEE_Result huk_crypt(TEE_OperationMode mode, const uint8_t *in, + size_t in_sz, uint8_t *out, size_t *out_sz) +{ + TEE_Result res = TEE_ERROR_GENERIC; + TEE_OperationHandle crypto_op = TEE_HANDLE_NULL; + TEE_ObjectHandle hkey = TEE_HANDLE_NULL; + uint8_t huk_key[TA_DERIVED_KEY_MAX_SIZE] = { }; + TEE_Attribute attr = { }; + uint8_t *local_in = NULL; + uint8_t *local_out = NULL; + size_t local_out_sz = 0; + + local_in = TEE_Malloc(in_sz, TEE_MALLOC_FILL_ZERO); + if (!local_in) + return TEE_ERROR_OUT_OF_MEMORY; + memcpy(local_in, in, in_sz); + + if (ADD_OVERFLOW(in_sz, SEALING_OVERHEAD, &local_out_sz)) { + TEE_Free(local_in); + return TEE_ERROR_SECURITY; + } + local_out = TEE_Malloc(local_out_sz, TEE_MALLOC_FILL_ZERO); + if (!local_out) { + TEE_Free(local_in); + return TEE_ERROR_OUT_OF_MEMORY; + } + + res = TEE_AllocateOperation(&crypto_op, TEE_ALG_AES_GCM, mode, + sizeof(huk_key) * 8); + if (res) + goto out_bufs; + + res = derive_unique_key(huk_key, sizeof(huk_key), + sealing_op_derivation_extra, + sizeof(sealing_op_derivation_extra) - 1); + if (res) { + EMSG("derive_unique_key failed: returned %#"PRIx32, res); + goto out_op; + } + + res = TEE_AllocateTransientObject(TEE_TYPE_AES, sizeof(huk_key) * 8, + &hkey); + if (res) + goto out_op; + + TEE_InitRefAttribute(&attr, TEE_ATTR_SECRET_VALUE, huk_key, + sizeof(huk_key)); + + res = TEE_PopulateTransientObject(hkey, &attr, 1); + if (res) + goto out_key; + + res = TEE_SetOperationKey(crypto_op, hkey); + if (res) + goto out_key; + + if (mode == TEE_MODE_ENCRYPT) { + res = huk_ae_encrypt(crypto_op, local_in, in_sz, local_out, + out_sz); + if (res) + DMSG("huk_AE_encrypt failed: returned %#"PRIx32, res); + } else if (mode == TEE_MODE_DECRYPT) { + res = huk_ae_decrypt(crypto_op, local_in, in_sz, local_out, + out_sz); + if (res) + DMSG("huk_AE_decrypt failed: returned %#"PRIx32, res); + } else { + TEE_Panic(0); + } + + if (res == TEE_SUCCESS) + memcpy(out, local_out, *out_sz); + +out_key: + TEE_FreeTransientObject(hkey); +out_op: + TEE_FreeOperation(crypto_op); + memzero_explicit(huk_key, sizeof(huk_key)); +out_bufs: + memzero_explicit(local_in, in_sz); + TEE_Free(local_in); + memzero_explicit(local_out, local_out_sz); + TEE_Free(local_out); + return res; +} + +static TEE_Result seal_secret(uint32_t types, + TEE_Param params[TEE_NUM_PARAMS]) +{ + TEE_Result res = TEE_SUCCESS; + uint8_t *in = NULL; + size_t in_sz = 0; + uint8_t *out = NULL; + size_t out_sz = 0; + size_t sealed_sz = 0; + + DMSG("Invoked TA_APPSECRETS_CMD_SEAL_SECRET"); + + if (types != TEE_PARAM_TYPES(TEE_PARAM_TYPE_MEMREF_INPUT, + TEE_PARAM_TYPE_MEMREF_OUTPUT, + TEE_PARAM_TYPE_NONE, + TEE_PARAM_TYPE_NONE)) + return TEE_ERROR_BAD_PARAMETERS; + + in = params[0].memref.buffer; + in_sz = params[0].memref.size; + out = params[1].memref.buffer; + out_sz = params[1].memref.size; + + if (!in || !in_sz || in_sz > (MAX_BUF_SIZE - SEALING_OVERHEAD)) + return TEE_ERROR_BAD_PARAMETERS; + if (!out && out_sz) + return TEE_ERROR_BAD_PARAMETERS; + + if (ADD_OVERFLOW(in_sz, SEALING_OVERHEAD, &sealed_sz) || + sealed_sz > out_sz) { + params[1].memref.size = sealed_sz; + return TEE_ERROR_SHORT_BUFFER; + } + + res = huk_crypt(TEE_MODE_ENCRYPT, in, in_sz, out, &out_sz); + if (res == TEE_SUCCESS) { + assert(out_sz == in_sz + SEALING_OVERHEAD); + params[1].memref.size = out_sz; + } + + return res; +} + +static TEE_Result unseal_secret(uint32_t types, + TEE_Param params[TEE_NUM_PARAMS]) +{ + TEE_Result res = TEE_SUCCESS; + TEE_Identity id = { 0 }; + uint8_t *in = NULL; + size_t in_sz = 0; + uint8_t *out = NULL; + size_t out_sz = 0; + uint8_t *decrypted = NULL; + size_t decrypted_sz = 0; + size_t decrypted_alloc_sz = 0; + struct plaintext_payload *payload = NULL; + size_t user_data_sz = 0; + size_t unsealed_sz = 0; + + DMSG("Invoked TA_APPSECRETS_CMD_UNSEAL_SECRET"); + + if (types != TEE_PARAM_TYPES(TEE_PARAM_TYPE_MEMREF_INPUT, + TEE_PARAM_TYPE_MEMREF_OUTPUT, + TEE_PARAM_TYPE_NONE, + TEE_PARAM_TYPE_NONE)) + return TEE_ERROR_BAD_PARAMETERS; + + in = params[0].memref.buffer; + in_sz = params[0].memref.size; + out = params[1].memref.buffer; + out_sz = params[1].memref.size; + + if (!in || in_sz <= sizeof(struct secret_blob_hdr) || + in_sz > MAX_BUF_SIZE) + return TEE_ERROR_BAD_PARAMETERS; + if (!out && out_sz) + return TEE_ERROR_BAD_PARAMETERS; + + if (in_sz < SEALING_OVERHEAD) + return TEE_ERROR_SECURITY; + + if (SUB_OVERFLOW(in_sz, SEALING_OVERHEAD, &unsealed_sz) || + unsealed_sz > out_sz) { + params[1].memref.size = unsealed_sz; + return TEE_ERROR_SHORT_BUFFER; + } + + res = TEE_GetPropertyAsIdentity(TEE_PROPSET_CURRENT_CLIENT, + "gpd.client.identity", &id); + if (res != TEE_SUCCESS) { + EMSG("Failed to get client identity: 0x%08x", res); + return res; + } + + if (SUB_OVERFLOW(in_sz, sizeof(struct secret_blob_hdr), &decrypted_sz)) + return TEE_ERROR_SECURITY; + decrypted_alloc_sz = decrypted_sz; + decrypted = TEE_Malloc(decrypted_alloc_sz, TEE_MALLOC_FILL_ZERO); + if (!decrypted) + return TEE_ERROR_OUT_OF_MEMORY; + + res = huk_crypt(TEE_MODE_DECRYPT, in, in_sz, decrypted, &decrypted_sz); + if (res != TEE_SUCCESS) + goto out; + + assert(decrypted_sz == in_sz - sizeof(struct secret_blob_hdr)); + + if (SUB_OVERFLOW(decrypted_sz, sizeof(struct plaintext_payload), + &user_data_sz)) { + res = TEE_ERROR_SECURITY; + goto out; + } + + payload = (struct plaintext_payload *)(void *)decrypted; + if (payload->client_login != id.login || + TEE_MemCompare(&payload->client_uuid, &id.uuid, sizeof(TEE_UUID))) { + DMSG("Client identity mismatch"); + res = TEE_ERROR_SECURITY; + goto out; + } + + if (user_data_sz > out_sz) { + params[1].memref.size = user_data_sz; + res = TEE_ERROR_SHORT_BUFFER; + goto out; + } + + if (out && user_data_sz) + memcpy(out, payload->data, user_data_sz); + params[1].memref.size = user_data_sz; + +out: + memzero_explicit(decrypted, decrypted_alloc_sz); + TEE_Free(decrypted); + return res; +} + +TEE_Result TA_CreateEntryPoint(void) +{ + return TEE_SUCCESS; +} + +void TA_DestroyEntryPoint(void) +{ +} + +TEE_Result TA_OpenSessionEntryPoint(uint32_t pt __unused, + TEE_Param params[TEE_NUM_PARAMS] __unused, + void **session __unused) +{ + return TEE_SUCCESS; +} + +void TA_CloseSessionEntryPoint(void *sess __unused) +{ +} + +TEE_Result TA_InvokeCommandEntryPoint(void *sess __unused, uint32_t cmd, + uint32_t pt, + TEE_Param params[TEE_NUM_PARAMS]) +{ + switch (cmd) { + case TA_APPSECRETS_CMD_SEAL_SECRET: + return seal_secret(pt, params); + case TA_APPSECRETS_CMD_UNSEAL_SECRET: + return unseal_secret(pt, params); + default: + EMSG("Command ID %#"PRIx32" is not supported", cmd); + return TEE_ERROR_NOT_SUPPORTED; + } +} diff --git a/ta/app_secrets/include/app_secrets_ta.h b/ta/app_secrets/include/app_secrets_ta.h new file mode 100644 index 00000000000..96be794e76b --- /dev/null +++ b/ta/app_secrets/include/app_secrets_ta.h @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * Copyright (c) 2026, Vaisala Oyj. + */ + +#ifndef APP_SECRETS_TA_H +#define APP_SECRETS_TA_H + +#define APP_SECRETS_TA_UUID \ + { 0x5ca4d9d9, 0xdee4, 0x47f4, \ + { 0x97, 0x7a, 0x7e, 0xad, 0xc0, 0x60, 0xe5, 0x2c } } + +/* + * Seal secret using hardware unique TA specific key + * + * [in] memref[0] Plain secret + * [out] memref[1] Sealed secret datablob + */ +#define TA_APPSECRETS_CMD_SEAL_SECRET 0x0 + +/* + * Unseal secret using hardware unique TA specific key + * + * [in] memref[0] Sealed secret datablob + * [out] memref[1] Plain secret + */ +#define TA_APPSECRETS_CMD_UNSEAL_SECRET 0x1 + +#endif /* APP_SECRETS_TA_H */ diff --git a/ta/app_secrets/sub.mk b/ta/app_secrets/sub.mk new file mode 100644 index 00000000000..15309589ac5 --- /dev/null +++ b/ta/app_secrets/sub.mk @@ -0,0 +1,3 @@ +global-incdirs-y += include +global-incdirs-y += . +srcs-y += app_secrets_ta.c diff --git a/ta/app_secrets/user_ta.mk b/ta/app_secrets/user_ta.mk new file mode 100644 index 00000000000..360ae264c72 --- /dev/null +++ b/ta/app_secrets/user_ta.mk @@ -0,0 +1 @@ +user-ta-uuid := 5ca4d9d9-dee4-47f4-977a-7eadc060e52c diff --git a/ta/app_secrets/user_ta_header_defines.h b/ta/app_secrets/user_ta_header_defines.h new file mode 100644 index 00000000000..f3792e9cfd8 --- /dev/null +++ b/ta/app_secrets/user_ta_header_defines.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * Copyright (c) 2026, Vaisala Oyj. + */ + +#ifndef USER_TA_HEADER_DEFINES_H +#define USER_TA_HEADER_DEFINES_H + +#include + +#define TA_UUID APP_SECRETS_TA_UUID + +#define TA_FLAGS (TA_FLAG_SINGLE_INSTANCE | \ + TA_FLAG_MULTI_SESSION) + +#define TA_STACK_SIZE (4 * 1024) +#define TA_DATA_SIZE (16 * 1024) + +#define TA_VERSION "1.0" + +#define TA_DESCRIPTION "Application Secrets TA" + +#endif /* USER_TA_HEADER_DEFINES_H */