From 1d8729fa9442069930bd76fb1a2cfeb636e3e68e Mon Sep 17 00:00:00 2001 From: David Garske Date: Thu, 29 Oct 2026 09:22:05 -0700 Subject: [PATCH] Add OP-TEE trusted application support for the fwTPM --- CMakeLists.txt | 1 + configure.ac | 28 ++ hal/tpm_io.c | 4 +- hal/tpm_io.h | 2 +- src/fwtpm/README.md | 2 +- src/fwtpm/fwtpm_command.c | 2 - src/fwtpm/fwtpm_nv.c | 13 +- src/fwtpm/ports/optee/README.md | 122 +++++++++ src/fwtpm/ports/optee/fwtpm_optee_nv.c | 346 +++++++++++++++++++++++++ src/fwtpm/ports/optee/fwtpm_optee_ta.c | 217 ++++++++++++++++ src/include.am | 3 + src/tpm2.c | 12 +- src/tpm2_optee.c | 240 +++++++++++++++++ src/tpm2_spdm.c | 2 +- src/tpm2_wrap.c | 10 +- wolftpm/fwtpm/fwtpm.h | 6 +- wolftpm/fwtpm/fwtpm_nv.h | 5 +- wolftpm/fwtpm/fwtpm_optee.h | 166 ++++++++++++ wolftpm/include.am | 2 + wolftpm/tpm2_optee.h | 63 +++++ wolftpm/tpm2_types.h | 29 ++- 21 files changed, 1257 insertions(+), 18 deletions(-) create mode 100644 src/fwtpm/ports/optee/README.md create mode 100644 src/fwtpm/ports/optee/fwtpm_optee_nv.c create mode 100644 src/fwtpm/ports/optee/fwtpm_optee_ta.c create mode 100644 src/tpm2_optee.c create mode 100644 wolftpm/fwtpm/fwtpm_optee.h create mode 100644 wolftpm/tpm2_optee.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 1f4ca39eb..a0f2b0c6a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -84,6 +84,7 @@ set(TPM_SOURCES src/tpm2_packet.c src/tpm2_param_enc.c src/tpm2_swtpm.c + src/tpm2_optee.c src/tpm2_tis.c src/tpm2_winapi.c src/tpm2_wrap.c diff --git a/configure.ac b/configure.ac index c90bdc20b..8c58ca3a5 100644 --- a/configure.ac +++ b/configure.ac @@ -343,6 +343,33 @@ fi AC_SUBST([SWTPM_PORT]) AC_SUBST([DISTCHECK_SWTPM_PORT_FLAG]) +# fwTPM as an OP-TEE trusted application, reached over libteec +AC_ARG_ENABLE([optee], + [AS_HELP_STRING([--enable-optee],[Enable use of the wolfTPM fwTPM running as an OP-TEE trusted application, reached over libteec (default: disabled)])], + [ ENABLED_OPTEE=$enableval ], + [ ENABLED_OPTEE=no ] + ) + +if test "x$ENABLED_OPTEE" = "xyes" +then + if test "x$ENABLED_DEVTPM" = "xyes" + then + AC_MSG_ERROR([Cannot enable both optee and devtpm]) + fi + # swtpm is the default on Linux x86_64/aarch64, which is where an OP-TEE + # client runs, so say which flag to pass rather than just refusing. + if test "x$ENABLED_SWTPM" = "xyes" || test "x$ENABLED_SWTPM" = "xuart" + then + AC_MSG_ERROR([Cannot enable both optee and swtpm; add --disable-swtpm]) + fi + AC_CHECK_HEADER([tee_client_api.h], [], + [AC_MSG_ERROR([--enable-optee requires tee_client_api.h from the OP-TEE client])]) + AC_CHECK_LIB([teec], [TEEC_InitializeContext], [], + [AC_MSG_ERROR([--enable-optee requires libteec from the OP-TEE client])]) + AM_CFLAGS="$AM_CFLAGS -DWOLFTPM_OPTEE" +fi +AM_CONDITIONAL([BUILD_OPTEE], [test "x$ENABLED_OPTEE" = "xyes"]) + # Firmware TPM (fwTPM) - software TPM 2.0 simulator AC_ARG_ENABLE([fwtpm], [AS_HELP_STRING([--enable-fwtpm],[Enable firmware TPM (fwTPM) server (default: enabled on Linux x86_64/aarch64, disabled elsewhere or when any --enable-/--enable-spi/--enable-i2c/--enable-mmio/--enable-devtpm/--enable-autodetect is explicitly set)])], @@ -1261,6 +1288,7 @@ echo " * SPI: $SPI_ACTUAL" echo " * I2C: $ENABLED_I2C" echo " * Linux kernel TPM device: $ENABLED_DEVTPM" echo " * SWTPM: $ENABLED_SWTPM" +echo " * OP-TEE fwTPM TA: $ENABLED_OPTEE" if test "x$ENABLED_SWTPM" = "xyes"; then echo " * SWTPM Port: $SWTPM_PORT" fi diff --git a/hal/tpm_io.c b/hal/tpm_io.c index ade038c9a..a561b81e6 100644 --- a/hal/tpm_io.c +++ b/hal/tpm_io.c @@ -43,9 +43,11 @@ /* --- BEGIN IO Callback Logic -- */ /******************************************************************************/ -/* Native Windows, native Linux and TPM Simulator do not need an IO callback */ +/* Native Windows, native Linux, TPM Simulator and the OP-TEE trusted + * application do not need an IO callback */ #if ! (defined(WOLFTPM_LINUX_DEV) || \ defined(WOLFTPM_SWTPM) || \ + defined(WOLFTPM_OPTEE) || \ defined(WOLFTPM_WINAPI) ) && \ (defined(WOLFTPM_EXAMPLE_HAL) || defined(WOLFTPM_MMIO) || \ defined(WOLFTPM_FWTPM_HAL)) diff --git a/hal/tpm_io.h b/hal/tpm_io.h index 1d4da11be..09b97dc32 100644 --- a/hal/tpm_io.h +++ b/hal/tpm_io.h @@ -57,7 +57,7 @@ */ #if defined(WOLFTPM_LINUX_DEV) || defined(WOLFTPM_SWTPM) || \ - defined(WOLFTPM_WINAPI) + defined(WOLFTPM_OPTEE) || defined(WOLFTPM_WINAPI) /* HAL not required, so use NULL */ #define TPM2_IoCb NULL diff --git a/src/fwtpm/README.md b/src/fwtpm/README.md index 047a07125..c58697b34 100644 --- a/src/fwtpm/README.md +++ b/src/fwtpm/README.md @@ -165,7 +165,7 @@ compile-time macros (feature groups, algorithm flags, size limits, stack/heap control), spec version targeting, and HAL abstraction details. Key options: `--enable-fwtpm`, `--enable-fwtpm-only`, `--enable-swtpm`, -`--enable-fwtpm-small-ctx`, `--enable-fuzz`. Feature disable macros: +`--enable-fwtpm-nv-appendonly`, `--enable-fuzz`. Feature disable macros: `FWTPM_NO_ATTESTATION`, `FWTPM_NO_NV`, `FWTPM_NO_POLICY`, `FWTPM_NO_CREDENTIAL`, `FWTPM_NO_DA`, `FWTPM_NO_PARAM_ENC`, `FWTPM_NO_KEY_MIGRATION`, `FWTPM_NO_ECDH`, `FWTPM_NO_HASH_CMDS`, `FWTPM_NO_CONTEXT`, `FWTPM_NO_SYM_ENCRYPT`, diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index b3dac5268..b54bf6f59 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -20782,8 +20782,6 @@ int FWTPM_ProcessCommand(FWTPM_CTX* ctx, sizeof(ctx->activeCmdAuthIsPolicy)); #endif - /* rspSize is in/out: capacity in, bytes written out. Callers that leave - * it unset get the historic FWTPM_MAX_COMMAND_SIZE assumption. */ /* Handlers commit state before marshalling and some write outside the * packet API, so they are only ever run with a full-size buffer. A * caller with a smaller transport buffer must stage through one. diff --git a/src/fwtpm/fwtpm_nv.c b/src/fwtpm/fwtpm_nv.c index 10ab68eb4..c86dce013 100644 --- a/src/fwtpm/fwtpm_nv.c +++ b/src/fwtpm/fwtpm_nv.c @@ -2331,9 +2331,20 @@ int FWTPM_NV_Init(FWTPM_CTX* ctx) /* No valid NV image — generate fresh hierarchy seeds */ if (rc == TPM_RC_SUCCESS && hdr.magic == FWTPM_NV_MAGIC && hdr.version != FWTPM_NV_VERSION) { + /* Unguarded on purpose: it reports that all NV state was just + * discarded. libutee has no stdio streams, so a trusted + * application cannot link fprintf; every other build keeps + * stderr, so nothing starts writing diagnostics to a stdout that + * the caller may be using as a data channel. */ + #ifdef WOLFSSL_OPTEE + printf("WARNING: fwTPM NV version mismatch " + "(found %d, expected %d), regenerating seeds\n", + (int)hdr.version, (int)FWTPM_NV_VERSION); + #else fprintf(stderr, "WARNING: fwTPM NV version mismatch " - "(found %d, expected %d) — regenerating seeds\n", + "(found %d, expected %d), regenerating seeds\n", (int)hdr.version, (int)FWTPM_NV_VERSION); + #endif } rc = FwNvGenFreshState(ctx); } diff --git a/src/fwtpm/ports/optee/README.md b/src/fwtpm/ports/optee/README.md new file mode 100644 index 000000000..678aa72ed --- /dev/null +++ b/src/fwtpm/ports/optee/README.md @@ -0,0 +1,122 @@ +# fwTPM on OP-TEE + +The wolfTPM fwTPM running as an OP-TEE trusted application, with the TPM 2.0 +command processor in the secure world and the client in the normal world. + +Platform independent: these two sources need only the TEE Internal Core API +and OP-TEE secure storage. A board port supplies `user_ta_header_defines.h` +(UUID, flags, stack and heap sizes), the wolfCrypt platform shims and the +build, sign and deploy steps. + +| File | Role | +|------|------| +| `wolftpm/fwtpm/fwtpm_optee.h` | The trusted application's UUID, the command IDs shared with the normal-world client, and the NV HAL and clock HAL declarations | +| `fwtpm_optee_ta.c` | `TA_*EntryPoint` functions; one invocation carries one whole TPM command | +| `fwtpm_optee_nv.c` | NV journal on OP-TEE secure storage, with the journal integrity key derived from the hardware unique key | + +## Transport + +`FWTPM_ProcessCommand` takes a command buffer and returns a response buffer, +which is exactly the shape of `TA_InvokeCommandEntryPoint`, so no IO HAL and +no TIS layer is involved. `FWTPM_IO_ServerLoop` and `FWTPM_TIS_ServerLoop` +both block and cannot be used in a trusted application, so `fwtpm_io.c`, +`fwtpm_tis.c`, `fwtpm_tis_shm.c` and `fwtpm_main.c` are left out of the +build. + +Both directions are staged through static buffers of `FWTPM_MAX_COMMAND_SIZE`. +The response buffer must be that large whatever the client's capacity, and +staging the command as well means the normal world cannot change it after it +has been parsed. + +`locality` is fixed at `FWTPM_OPTEE_LOCALITY` and never taken from the +client: it is stored verbatim into the command context without validation. + +## NV storage + +Secure storage is byte addressable and fully rewritable, so the journal runs +in its default mode with `appendOnly` and `writeAlign` left at 0. The +append-only build option is for write-once flash and is not needed here. + +A read past the end of the stream is reported as erased (`0xFF`), which is how +the journal recognizes unwritten NV and decides to generate fresh seeds on +first boot. + +`get_integrity_key` asks the system PTA for a key derived from the hardware +unique key and bound to the TA UUID. Platforms built without the system PTA +fall back to a random key in its own secure storage object. Secure storage is +itself hardware-unique-key encrypted and TA bound, so the journal MAC is +defense in depth. + +## wolfCrypt platform layer + +None is needed here. wolfSSL maps `XMALLOC`, `XFREE` and `XREALLOC` onto +`TEE_Malloc`, `TEE_Free` and `TEE_Realloc`, and seeds its DRBG from +`TEE_GenerateRandom`, whenever `WOLFSSL_OPTEE` is defined. A port does not +supply shims or set `XMALLOC_OVERRIDE`. + +Where wolfCrypt is not compiled in at all (`WOLFTPM2_NO_WOLFCRYPT`), +`wolftpm/tpm2_types.h` does the same for its own `XMALLOC` and `XFREE`. +`WOLFTPM2_NO_HEAP` still wins there and leaves both undefined. + +## Required wolfCrypt options + +A trusted application is built from a `user_settings.h` with no configure +step, so nothing checks the wolfCrypt configuration the way wolfTPM's +configure does. The fwTPM needs, beyond the obvious RSA, ECC, AES-CFB, +HMAC/HKDF, SHA-1/2/3 and ASN.1: + +| Option | Why | +|--------|-----| +| `WOLFSSL_OPTEE` | names the environment, and brings in the heap and RNG mapping above | +| `WOLFSSL_KEY_GEN` | the TPM generates its own primary keys | +| `WC_RSA_PSS` | TPM 2.0 RSA signing schemes | +| `WC_RSA_NO_PADDING` | raw RSA; without it those operations return `TPM_RC_SCHEME` | +| `WOLFSSL_AES_CFB`, `WOLFSSL_AES_DIRECT` | TPM 2.0 parameter encryption | +| `WOLFSSL_NO_DER_TO_PEM` | `WOLFSSL_KEY_GEN` otherwise pulls in `wc_DerToPemEx`, which needs a `strncat` libutee does not provide | +| `USE_WOLF_STRCASECMP`, `USE_WOLF_STRNCASECMP` | libutee has neither | +| `WOLFSSL_HAVE_MLDSA`, `WOLFSSL_HAVE_MLKEM` | the v1.85 post-quantum commands | + +`WOLFSSL_OPTEE` is not optional: the trusted application toolchain is a Linux +cross compiler and predefines `__unix__`, so without it wolfCrypt compiles +descriptor and socket code a trusted application cannot provide. + +OP-TEE's `compiler.h` defines a `fallthrough` macro. wolfCrypt only notices +it if `compiler.h` was included first and otherwise settles on +`__attribute__((fallthrough))`, which then expands through OP-TEE's macro and +nests the attribute inside itself, so define `FALL_THROUGH` explicitly. + +## Client + +Build wolfTPM with `WOLFTPM_OPTEE` to reach the TA over `libteec`. One +`TEEC_InvokeCommand` maps onto one `FWTPM_ProcessCommand`, following the +`WOLFTPM_SWTPM` whole-command pattern rather than emulating TIS registers, +which would cost a world switch per register access. + +One session per process: the client holds the TEE session in file scope, so +two `TPM2_CTX` in one process share it and `TPM2_Cleanup` on either closes it. +The other reconnects on its next command, but the trusted application flushes +transient objects on session close, so transient handles do not survive. Create +and use a key in one context, or make the handle persistent with +`TPM2_EvictControl`. + +## UUID + +OP-TEE identifies a trusted application by UUID, and the same value has to +appear in the trusted application, in the signed binary's filename and in the +client, which are built separately. `WOLFTPM_OPTEE_TA_UUID_STR` and the `_P1` +to `_P4` macros beside it in `wolftpm/fwtpm/fwtpm_optee.h` are the one +definition; the client builds `WOLFTPM_OPTEE_TA_UUID` from them and a board +port generates its `TA_UUID` and binary name from them. A mismatch appears +only at run time, as `TEEC_OpenSession` returning `0xffff0008`. + +## Build order + + XMEMSET(&ctx, 0, sizeof(ctx)); + FWTPM_OPTEE_NV_SetHAL(&ctx, &nv); /* before FWTPM_Init */ + FWTPM_Clock_SetHAL(&ctx, FWTPM_OPTEE_Clock_GetMs, NULL); /* optional */ + FWTPM_Init(&ctx); + +## Reference port + +NVIDIA Jetson Orin: see `NVIDIA/fwtpm-jetson-optee` in +[wolftpm-examples](https://github.com/wolfSSL/wolftpm-examples). diff --git a/src/fwtpm/ports/optee/fwtpm_optee_nv.c b/src/fwtpm/ports/optee/fwtpm_optee_nv.c new file mode 100644 index 000000000..dbd440f22 --- /dev/null +++ b/src/fwtpm/ports/optee/fwtpm_optee_nv.c @@ -0,0 +1,346 @@ +/* fwtpm_optee_nv.c + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfTPM. + * + * wolfTPM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTPM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +/* fwTPM NV journal on OP-TEE secure storage. + * + * Secure storage is byte addressable and fully rewritable, so the journal + * runs in its default mode: appendOnly and writeAlign stay 0 and no + * append-only build option is needed. The backing object is encrypted with + * the hardware unique key and bound to this TA's UUID by the TEE, so the + * journal MAC on top of it is defense in depth rather than the only + * protection. + */ + +#include + +#include + +/* Only an OP-TEE trusted application build has libutee. WOLFSSL_OPTEE is what + * says so and is what wolfCrypt keys its own TEE support off; WOLFTPM_FWTPM + * alone is not enough, since a plain Linux fwTPM build defines that too and + * would then compile this against headers it does not have. */ +#if defined(WOLFTPM_FWTPM) && defined(WOLFSSL_OPTEE) + +#include + +/* An erased byte reads as 0xFF, matching the convention the journal uses to + * recognize unwritten NV. Secure storage reads short at end of stream rather + * than returning erased bytes, so the gap is filled here. */ +#define FWTPM_OPTEE_ERASED_BYTE 0xFF + +/* Chunk used to write filler without a large stack buffer. */ +#define FWTPM_OPTEE_FILL_CHUNK 256 + +/* Salt for the TA unique key derivation. Changing it changes the derived + * key and makes an existing journal fail its integrity check. */ +#define FWTPM_OPTEE_KEY_SALT "wolfTPM fwTPM NV journal" + +static int FwOpteeNvFill(TEE_ObjectHandle obj, word32 offset, word32 size) +{ + byte chunk[FWTPM_OPTEE_FILL_CHUNK]; + TEE_Result res; + word32 left = size; + word32 n; + + XMEMSET(chunk, FWTPM_OPTEE_ERASED_BYTE, sizeof(chunk)); + + res = TEE_SeekObjectData(obj, (intmax_t)offset, TEE_DATA_SEEK_SET); + if (res != TEE_SUCCESS) { + return TPM_RC_FAILURE; + } + while (left > 0) { + n = (left > sizeof(chunk)) ? (word32)sizeof(chunk) : left; + res = TEE_WriteObjectData(obj, chunk, (size_t)n); + if (res != TEE_SUCCESS) { + return TPM_RC_FAILURE; + } + left -= n; + } + return TPM_RC_SUCCESS; +} + +static int FwOpteeNvRead(void* ctx, word32 offset, byte* buf, word32 size) +{ + FWTPM_OPTEE_NV* nv = (FWTPM_OPTEE_NV*)ctx; + TEE_Result res; + size_t got = 0; + + if (nv == NULL || buf == NULL || nv->obj == TEE_HANDLE_NULL) { + return TPM_RC_FAILURE; + } + if (size == 0) { + return TPM_RC_SUCCESS; + } + if (offset > nv->maxSize || size > nv->maxSize - offset) { + return TPM_RC_FAILURE; + } + + res = TEE_SeekObjectData(nv->obj, (intmax_t)offset, TEE_DATA_SEEK_SET); + if (res != TEE_SUCCESS) { + return TPM_RC_FAILURE; + } + res = TEE_ReadObjectData(nv->obj, buf, (size_t)size, &got); + if (res != TEE_SUCCESS) { + return TPM_RC_FAILURE; + } + /* Short read means end of stream: the rest of the region has never been + * written, so report it erased. */ + if (got < (size_t)size) { + XMEMSET(buf + got, FWTPM_OPTEE_ERASED_BYTE, (size_t)size - got); + } + return TPM_RC_SUCCESS; +} + +static int FwOpteeNvWrite(void* ctx, word32 offset, const byte* buf, + word32 size) +{ + FWTPM_OPTEE_NV* nv = (FWTPM_OPTEE_NV*)ctx; + TEE_ObjectInfo info; + TEE_Result res; + int rc; + + if (nv == NULL || buf == NULL || nv->obj == TEE_HANDLE_NULL) { + return TPM_RC_FAILURE; + } + if (size == 0) { + return TPM_RC_SUCCESS; + } + if (offset > nv->maxSize || size > nv->maxSize - offset) { + return TPM_RC_FAILURE; + } + + /* A seek past the end of the stream makes the TEE fill the gap with + * zeroes, which the journal would read as written data rather than as + * erased NV. Fill it explicitly instead. */ + res = TEE_GetObjectInfo1(nv->obj, &info); + if (res != TEE_SUCCESS) { + return TPM_RC_FAILURE; + } + if (offset > (word32)info.dataSize) { + rc = FwOpteeNvFill(nv->obj, (word32)info.dataSize, + offset - (word32)info.dataSize); + if (rc != TPM_RC_SUCCESS) { + return rc; + } + } + + res = TEE_SeekObjectData(nv->obj, (intmax_t)offset, TEE_DATA_SEEK_SET); + if (res != TEE_SUCCESS) { + return TPM_RC_FAILURE; + } + res = TEE_WriteObjectData(nv->obj, buf, (size_t)size); + if (res != TEE_SUCCESS) { + return TPM_RC_FAILURE; + } + return TPM_RC_SUCCESS; +} + +static int FwOpteeNvErase(void* ctx, word32 offset, word32 size) +{ + FWTPM_OPTEE_NV* nv = (FWTPM_OPTEE_NV*)ctx; + TEE_ObjectInfo info; + TEE_Result res; + + if (nv == NULL || nv->obj == TEE_HANDLE_NULL) { + return TPM_RC_FAILURE; + } + if (size == 0) { + return TPM_RC_SUCCESS; + } + if (offset > nv->maxSize || size > nv->maxSize - offset) { + return TPM_RC_FAILURE; + } + + res = TEE_GetObjectInfo1(nv->obj, &info); + if (res != TEE_SUCCESS) { + return TPM_RC_FAILURE; + } + /* Erasing to the end of the stream is the common case and truncation is + * cheaper than overwriting: a later read past the end reports erased. */ + if (offset + size >= (word32)info.dataSize) { + res = TEE_TruncateObjectData(nv->obj, (size_t)offset); + if (res != TEE_SUCCESS) { + return TPM_RC_FAILURE; + } + return TPM_RC_SUCCESS; + } + return FwOpteeNvFill(nv->obj, offset, size); +} + +/* Ask the system PTA to derive a key from the hardware unique key, bound to + * this TA's UUID. Not every platform builds the system PTA in, so the caller + * falls back when this fails. */ +static int FwOpteeDeriveTaKey(byte* key, word32 keySz) +{ + static const TEE_UUID sysUuid = PTA_SYSTEM_UUID; + TEE_TASessionHandle sess = TEE_HANDLE_NULL; + TEE_Param params[TEE_NUM_PARAMS]; + TEE_Result res; + uint32_t origin = 0; + uint32_t types; + + res = TEE_OpenTASession(&sysUuid, TEE_TIMEOUT_INFINITE, 0, NULL, &sess, + &origin); + if (res != TEE_SUCCESS) { + return TPM_RC_FAILURE; + } + + XMEMSET(params, 0, sizeof(params)); + params[0].memref.buffer = (void*)FWTPM_OPTEE_KEY_SALT; + params[0].memref.size = sizeof(FWTPM_OPTEE_KEY_SALT) - 1; + params[1].memref.buffer = key; + params[1].memref.size = keySz; + types = TEE_PARAM_TYPES(TEE_PARAM_TYPE_MEMREF_INPUT, + TEE_PARAM_TYPE_MEMREF_OUTPUT, + TEE_PARAM_TYPE_NONE, + TEE_PARAM_TYPE_NONE); + + res = TEE_InvokeTACommand(sess, TEE_TIMEOUT_INFINITE, + PTA_SYSTEM_DERIVE_TA_UNIQUE_KEY, types, params, &origin); + TEE_CloseTASession(sess); + + if (res != TEE_SUCCESS || params[1].memref.size != keySz) { + return TPM_RC_FAILURE; + } + return TPM_RC_SUCCESS; +} + +/* Fallback: a random key held in its own secure storage object. Secure + * storage is already hardware-unique-key encrypted and TA bound, so this is + * as strong as the journal it protects, just not re-derivable. */ +static int FwOpteeStoredKey(byte* key, word32 keySz) +{ + TEE_ObjectHandle obj = TEE_HANDLE_NULL; + TEE_Result res; + size_t got = 0; + + res = TEE_OpenPersistentObject(TEE_STORAGE_PRIVATE, + FWTPM_OPTEE_KEY_OBJECT_ID, sizeof(FWTPM_OPTEE_KEY_OBJECT_ID) - 1, + TEE_DATA_FLAG_ACCESS_READ, &obj); + if (res == TEE_SUCCESS) { + res = TEE_ReadObjectData(obj, key, (size_t)keySz, &got); + TEE_CloseObject(obj); + if (res != TEE_SUCCESS || got != (size_t)keySz) { + /* A short or failed read can still have written key bytes. */ + TPM2_ForceZero(key, keySz); + return TPM_RC_FAILURE; + } + return TPM_RC_SUCCESS; + } + if (res != TEE_ERROR_ITEM_NOT_FOUND) { + return TPM_RC_FAILURE; + } + + TEE_GenerateRandom(key, keySz); + res = TEE_CreatePersistentObject(TEE_STORAGE_PRIVATE, + FWTPM_OPTEE_KEY_OBJECT_ID, sizeof(FWTPM_OPTEE_KEY_OBJECT_ID) - 1, + TEE_DATA_FLAG_ACCESS_READ | TEE_DATA_FLAG_ACCESS_WRITE, + TEE_HANDLE_NULL, key, (size_t)keySz, &obj); + if (res != TEE_SUCCESS) { + TPM2_ForceZero(key, keySz); + return TPM_RC_FAILURE; + } + TEE_CloseObject(obj); + return TPM_RC_SUCCESS; +} + +static int FwOpteeNvIntegrityKey(void* ctx, byte* key, word32* keySz) +{ + int rc; + + (void)ctx; + /* keySz is output only: the journal zeroes it before the call, so it + * carries no capacity to check against. The buffer is FWTPM_NV_KEY_SIZE + * bytes, which is WC_SHA256_DIGEST_SIZE. */ + if (key == NULL || keySz == NULL) { + return TPM_RC_FAILURE; + } + + rc = FwOpteeDeriveTaKey(key, WC_SHA256_DIGEST_SIZE); + if (rc != TPM_RC_SUCCESS) { + rc = FwOpteeStoredKey(key, WC_SHA256_DIGEST_SIZE); + } + if (rc != TPM_RC_SUCCESS) { + return rc; + } + *keySz = WC_SHA256_DIGEST_SIZE; + return TPM_RC_SUCCESS; +} + +UINT64 FWTPM_OPTEE_Clock_GetMs(void* ctx) +{ + TEE_Time now; + + (void)ctx; + TEE_GetSystemTime(&now); + return ((UINT64)now.seconds * 1000) + (UINT64)now.millis; +} + +int FWTPM_OPTEE_NV_SetHAL(FWTPM_CTX* ctx, FWTPM_OPTEE_NV* nv) +{ + TEE_Result res; + + if (ctx == NULL || nv == NULL) { + return BAD_FUNC_ARG; + } + + nv->obj = TEE_HANDLE_NULL; + nv->maxSize = FWTPM_OPTEE_NV_MAX_SIZE; + + res = TEE_OpenPersistentObject(TEE_STORAGE_PRIVATE, + FWTPM_OPTEE_NV_OBJECT_ID, sizeof(FWTPM_OPTEE_NV_OBJECT_ID) - 1, + TEE_DATA_FLAG_ACCESS_READ | TEE_DATA_FLAG_ACCESS_WRITE, + &nv->obj); + if (res == TEE_ERROR_ITEM_NOT_FOUND) { + /* First boot: an empty object reads as erased, which is how the + * journal decides to generate fresh seeds. */ + res = TEE_CreatePersistentObject(TEE_STORAGE_PRIVATE, + FWTPM_OPTEE_NV_OBJECT_ID, sizeof(FWTPM_OPTEE_NV_OBJECT_ID) - 1, + TEE_DATA_FLAG_ACCESS_READ | TEE_DATA_FLAG_ACCESS_WRITE, + TEE_HANDLE_NULL, NULL, 0, &nv->obj); + } + if (res != TEE_SUCCESS) { + nv->obj = TEE_HANDLE_NULL; + return TPM_RC_FAILURE; + } + + /* Zeroed first: appendOnly is a bit-field and must not be indeterminate, + * or the journal can select write-once behavior. */ + XMEMSET(&nv->hal, 0, sizeof(nv->hal)); + nv->hal.read = FwOpteeNvRead; + nv->hal.write = FwOpteeNvWrite; + nv->hal.erase = FwOpteeNvErase; + nv->hal.get_integrity_key = FwOpteeNvIntegrityKey; + nv->hal.ctx = nv; + nv->hal.maxSize = nv->maxSize; + + return FWTPM_NV_SetHAL(ctx, &nv->hal); +} + +void FWTPM_OPTEE_NV_Close(FWTPM_OPTEE_NV* nv) +{ + if (nv != NULL && nv->obj != TEE_HANDLE_NULL) { + TEE_CloseObject(nv->obj); + nv->obj = TEE_HANDLE_NULL; + } +} + +#endif /* WOLFTPM_FWTPM && WOLFSSL_OPTEE */ diff --git a/src/fwtpm/ports/optee/fwtpm_optee_ta.c b/src/fwtpm/ports/optee/fwtpm_optee_ta.c new file mode 100644 index 000000000..2f6a1bca6 --- /dev/null +++ b/src/fwtpm/ports/optee/fwtpm_optee_ta.c @@ -0,0 +1,217 @@ +/* fwtpm_optee_ta.c + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfTPM. + * + * wolfTPM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTPM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +/* OP-TEE trusted application entry points for the fwTPM. + * + * One TA invocation carries one whole TPM command, so FWTPM_ProcessCommand + * is the entire transport. Neither server loop is used: FWTPM_IO_ServerLoop + * and FWTPM_TIS_ServerLoop both block, which a trusted application cannot + * do, so fwtpm_io.c, fwtpm_tis.c, fwtpm_tis_shm.c and fwtpm_main.c are not + * part of this build. + * + * A board port supplies user_ta_header_defines.h (UUID, flags, stack and + * heap sizes) and the wolfCrypt platform shims; nothing here is specific to + * any SoC. + */ + +#include + +#include + +/* Only an OP-TEE trusted application build has libutee. WOLFSSL_OPTEE is what + * says so and is what wolfCrypt keys its own TEE support off; WOLFTPM_FWTPM + * alone is not enough, since a plain Linux fwTPM build defines that too and + * would then compile this against headers it does not have. */ +#if defined(WOLFTPM_FWTPM) && defined(WOLFSSL_OPTEE) + +#include + +#include + +/* The TA is single instance, so one TPM. A second instance would be a second + * TPM sharing one NV journal. */ +static FWTPM_CTX fwCtx; +static FWTPM_OPTEE_NV fwNv; + +/* FWTPM_ProcessCommand requires a response buffer of at least + * FWTPM_MAX_COMMAND_SIZE whatever the caller's capacity, so both directions + * are staged through buffers of that size. Staging the command also removes + * the normal world's ability to change it after it has been parsed. */ +static byte fwCmdBuf[FWTPM_MAX_COMMAND_SIZE]; +static byte fwRspBuf[FWTPM_MAX_COMMAND_SIZE]; + +TEE_Result TA_CreateEntryPoint(void) +{ + int rc; + + XMEMSET(&fwCtx, 0, sizeof(fwCtx)); + XMEMSET(&fwNv, 0, sizeof(fwNv)); + + /* Before FWTPM_Init, which is what calls FWTPM_NV_Init. */ + rc = FWTPM_OPTEE_NV_SetHAL(&fwCtx, &fwNv); + if (rc != TPM_RC_SUCCESS) { + EMSG("fwTPM: secure storage NV unavailable (%d)", rc); + return TEE_ERROR_STORAGE_NOT_AVAILABLE; + } + + rc = FWTPM_Clock_SetHAL(&fwCtx, FWTPM_OPTEE_Clock_GetMs, NULL); + if (rc != TPM_RC_SUCCESS) { + FWTPM_OPTEE_NV_Close(&fwNv); + return TEE_ERROR_GENERIC; + } + + rc = FWTPM_Init(&fwCtx); + if (rc != TPM_RC_SUCCESS) { + EMSG("fwTPM: FWTPM_Init failed (%d)", rc); + FWTPM_OPTEE_NV_Close(&fwNv); + return TEE_ERROR_GENERIC; + } + + IMSG("fwTPM ready"); + return TEE_SUCCESS; +} + +void TA_DestroyEntryPoint(void) +{ + FWTPM_Cleanup(&fwCtx); + FWTPM_OPTEE_NV_Close(&fwNv); +} + +TEE_Result TA_OpenSessionEntryPoint(uint32_t paramTypes, + TEE_Param params[TEE_NUM_PARAMS], void** sessCtx) +{ + (void)paramTypes; + (void)params; + + *sessCtx = NULL; + return TEE_SUCCESS; +} + +void TA_CloseSessionEntryPoint(void* sessCtx) +{ + (void)sessCtx; + + /* So the next client cannot inherit transient objects, sessions or + * sequences from this one. */ + FWTPM_ResetCommandClient(&fwCtx); +} + +static TEE_Result FwOpteeSubmit(uint32_t paramTypes, + TEE_Param params[TEE_NUM_PARAMS]) +{ + int rc; + int rspSize; + uint32_t expected; + uint32_t expectedInOut; + uint32_t cmdSize; + + expected = TEE_PARAM_TYPES(TEE_PARAM_TYPE_MEMREF_INPUT, + TEE_PARAM_TYPE_MEMREF_OUTPUT, + TEE_PARAM_TYPE_NONE, + TEE_PARAM_TYPE_NONE); + /* Linux's tpm_ftpm_tee passes the response buffer as INOUT, since it + * carries both memrefs in one registered shared memory. Accept that too, + * so an in-kernel TPM driver can drive this and present a /dev/tpm + * device. Nothing is read out of the response buffer either way. */ + expectedInOut = TEE_PARAM_TYPES(TEE_PARAM_TYPE_MEMREF_INPUT, + TEE_PARAM_TYPE_MEMREF_INOUT, + TEE_PARAM_TYPE_NONE, + TEE_PARAM_TYPE_NONE); + if (paramTypes != expected && paramTypes != expectedInOut) { + return TEE_ERROR_BAD_PARAMETERS; + } + + cmdSize = params[0].memref.size; + if (params[0].memref.buffer == NULL || cmdSize == 0 || + cmdSize > sizeof(fwCmdBuf)) { + return TEE_ERROR_BAD_PARAMETERS; + } + XMEMCPY(fwCmdBuf, params[0].memref.buffer, cmdSize); + + rspSize = (int)sizeof(fwRspBuf); + rc = FWTPM_ProcessCommand(&fwCtx, fwCmdBuf, (int)cmdSize, + fwRspBuf, &rspSize, FWTPM_OPTEE_LOCALITY); + if (rc != TPM_RC_SUCCESS) { + /* A TPM level failure is encoded in the response and does not come + * back here, so this is a parse or transport error. */ + return TEE_ERROR_BAD_FORMAT; + } + if (rspSize <= 0) { + return TEE_ERROR_GENERIC; + } + + /* Short output buffer: report the size the client needs and let it + * retry, the usual GP convention. */ + if (params[1].memref.size < (uint32_t)rspSize) { + params[1].memref.size = (uint32_t)rspSize; + return TEE_ERROR_SHORT_BUFFER; + } + if (params[1].memref.buffer == NULL) { + return TEE_ERROR_BAD_PARAMETERS; + } + XMEMCPY(params[1].memref.buffer, fwRspBuf, (size_t)rspSize); + params[1].memref.size = (uint32_t)rspSize; + return TEE_SUCCESS; +} + +static TEE_Result FwOpteeGetInfo(uint32_t paramTypes, + TEE_Param params[TEE_NUM_PARAMS]) +{ + uint32_t expected; + + expected = TEE_PARAM_TYPES(TEE_PARAM_TYPE_VALUE_OUTPUT, + TEE_PARAM_TYPE_NONE, + TEE_PARAM_TYPE_NONE, + TEE_PARAM_TYPE_NONE); + if (paramTypes != expected) { + return TEE_ERROR_BAD_PARAMETERS; + } + params[0].value.a = FWTPM_OPTEE_ABI_VERSION; + params[0].value.b = FWTPM_MAX_COMMAND_SIZE; + return TEE_SUCCESS; +} + +TEE_Result TA_InvokeCommandEntryPoint(void* sessCtx, uint32_t cmdId, + uint32_t paramTypes, TEE_Param params[TEE_NUM_PARAMS]) +{ + TEE_Result res; + + (void)sessCtx; + + switch (cmdId) { + case FWTPM_OPTEE_CMD_SUBMIT: + res = FwOpteeSubmit(paramTypes, params); + break; + case FWTPM_OPTEE_CMD_RESET: + FWTPM_ResetCommandClient(&fwCtx); + res = TEE_SUCCESS; + break; + case FWTPM_OPTEE_CMD_GET_INFO: + res = FwOpteeGetInfo(paramTypes, params); + break; + default: + res = TEE_ERROR_NOT_SUPPORTED; + break; + } + return res; +} + +#endif /* WOLFTPM_FWTPM && WOLFSSL_OPTEE */ diff --git a/src/include.am b/src/include.am index c07f4a75e..31bc83045 100644 --- a/src/include.am +++ b/src/include.am @@ -19,6 +19,9 @@ src_libwolftpm_la_SOURCES = \ if BUILD_SWTPM src_libwolftpm_la_SOURCES += src/tpm2_swtpm.c endif +if BUILD_OPTEE +src_libwolftpm_la_SOURCES += src/tpm2_optee.c +endif if BUILD_WINAPI src_libwolftpm_la_SOURCES += src/tpm2_winapi.c src_libwolftpm_la_LIBADD += -ltbs diff --git a/src/tpm2.c b/src/tpm2.c index 8f74566a8..3de2c3bd6 100644 --- a/src/tpm2.c +++ b/src/tpm2.c @@ -35,6 +35,9 @@ #ifdef WOLFTPM_SWTPM #include #endif +#ifdef WOLFTPM_OPTEE + #include +#endif #ifdef WOLFTPM_WINAPI #include #endif @@ -78,6 +81,9 @@ static THREAD_LS_T TPM2_CTX* gActiveTPM; #elif defined(WOLFTPM_SWTPM) #define INTERNAL_SEND_COMMAND TPM2_SWTPM_SendCommand #define TPM2_INTERNAL_CLEANUP(ctx) +#elif defined(WOLFTPM_OPTEE) +#define INTERNAL_SEND_COMMAND TPM2_OPTEE_SendCommand +#define TPM2_INTERNAL_CLEANUP(ctx) #elif defined(WOLFTPM_WINAPI) #define INTERNAL_SEND_COMMAND TPM2_WinApi_SendCommand #define TPM2_INTERNAL_CLEANUP(ctx) TPM2_WinApi_Cleanup(ctx) @@ -1072,7 +1078,7 @@ TPM_RC TPM2_Init_ex(TPM2_CTX* ctx, TPM2HalIoCb ioCb, void* userCtx, #endif #if defined(WOLFTPM_LINUX_DEV) || defined(WOLFTPM_SWTPM) || \ - defined(WOLFTPM_WINAPI) + defined(WOLFTPM_WINAPI) || defined(WOLFTPM_OPTEE) if (ioCb != NULL || userCtx != NULL) { rc = BAD_FUNC_ARG; goto exit; @@ -1240,6 +1246,10 @@ TPM_RC TPM2_Cleanup(TPM2_CTX* ctx) TPM2_SwtpmClose(ctx); #endif +#ifdef WOLFTPM_OPTEE + TPM2_OpteeClose(ctx); +#endif + ctx->locality = TPM2_LOCALITY_UNINITIALIZED; return TPM_RC_SUCCESS; diff --git a/src/tpm2_optee.c b/src/tpm2_optee.c new file mode 100644 index 000000000..8c6667f4c --- /dev/null +++ b/src/tpm2_optee.c @@ -0,0 +1,240 @@ +/* tpm2_optee.c + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfTPM. + * + * wolfTPM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTPM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +/* Normal-world transport to the fwTPM running as an OP-TEE trusted + * application. See src/fwtpm/ports/optee for the secure-world side. */ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include + +#ifdef WOLFTPM_OPTEE + +#include +#include + +#include + +#include + +/* The trusted application is single instance, so one session per process is + * the right granularity, and these live here rather than in TPM2_CTX to keep + * tee_client_api.h out of tpm2.h, which every caller includes. + * + * The consequence is that a second TPM2_CTX in the same process shares one + * session, so TPM2_Cleanup on either closes it. The connect below is lazy, so + * the other context reconnects on its next command rather than failing, but + * the trusted application flushes transient objects when a session closes, so + * it loses any transient handles it was holding. That is the same hazard the + * Linux resource manager has when a file descriptor closes (see + * docs/DEVTPM.md): create and use in one context, or make the handle + * persistent with TPM2_EvictControl. */ +static TEEC_Context gTeeCtx; +static TEEC_Session gTeeSess; +static TEEC_SharedMemory gCmdShm; +static TEEC_SharedMemory gRspShm; +static int gTeeOpen = 0; + +static void OpteeDisconnect(void) +{ + if (gTeeOpen) { + TEEC_ReleaseSharedMemory(&gRspShm); + TEEC_ReleaseSharedMemory(&gCmdShm); + TEEC_CloseSession(&gTeeSess); + TEEC_FinalizeContext(&gTeeCtx); + gTeeOpen = 0; + } +} + +static int OpteeConnect(void) +{ + static const TEEC_UUID uuid = WOLFTPM_OPTEE_TA_UUID; + TEEC_Operation op; + TEEC_Result res; + uint32_t origin = 0; + uint32_t taCmdSize; + + if (gTeeOpen) { + return TPM_RC_SUCCESS; + } + + XMEMSET(&gTeeCtx, 0, sizeof(gTeeCtx)); + XMEMSET(&gTeeSess, 0, sizeof(gTeeSess)); + XMEMSET(&gCmdShm, 0, sizeof(gCmdShm)); + XMEMSET(&gRspShm, 0, sizeof(gRspShm)); + + res = TEEC_InitializeContext(NULL, &gTeeCtx); + if (res != TEEC_SUCCESS) { + #ifdef DEBUG_WOLFTPM + printf("TEEC_InitializeContext failed 0x%08x\n", (unsigned int)res); + #endif + return TPM_RC_FAILURE; + } + + res = TEEC_OpenSession(&gTeeCtx, &gTeeSess, &uuid, TEEC_LOGIN_PUBLIC, + NULL, NULL, &origin); + if (res != TEEC_SUCCESS) { + #ifdef DEBUG_WOLFTPM + printf("TEEC_OpenSession failed 0x%08x origin %u\n", + (unsigned int)res, (unsigned int)origin); + #endif + TEEC_FinalizeContext(&gTeeCtx); + return TPM_RC_FAILURE; + } + + /* Ask the trusted application for its ABI version and command size + * rather than assuming they match this build: MAX_RESPONSE_SIZE here and + * FWTPM_MAX_COMMAND_SIZE there are set by independent feature flags, and + * a response buffer smaller than the TA stages only fails at run time on + * whichever command first exceeds it. */ + XMEMSET(&op, 0, sizeof(op)); + op.paramTypes = TEEC_PARAM_TYPES(TEEC_VALUE_OUTPUT, TEEC_NONE, + TEEC_NONE, TEEC_NONE); + res = TEEC_InvokeCommand(&gTeeSess, FWTPM_OPTEE_CMD_GET_INFO, &op, + &origin); + if (res != TEEC_SUCCESS) { + TEEC_CloseSession(&gTeeSess); + TEEC_FinalizeContext(&gTeeCtx); + return TPM_RC_FAILURE; + } + if (op.params[0].value.a != FWTPM_OPTEE_ABI_VERSION) { + #ifdef DEBUG_WOLFTPM + printf("fwTPM TA ABI %u, client built for %u\n", + (unsigned int)op.params[0].value.a, + (unsigned int)FWTPM_OPTEE_ABI_VERSION); + #endif + TEEC_CloseSession(&gTeeSess); + TEEC_FinalizeContext(&gTeeCtx); + return TPM_RC_FAILURE; + } + taCmdSize = op.params[0].value.b; + + /* Registered once rather than per command: a temporary memref is + * registered and unregistered on every invoke, which for a TPM command + * rate is measurable. */ + gCmdShm.size = MAX_COMMAND_SIZE; + if (gCmdShm.size < taCmdSize) { + gCmdShm.size = taCmdSize; + } + gCmdShm.flags = TEEC_MEM_INPUT; + res = TEEC_AllocateSharedMemory(&gTeeCtx, &gCmdShm); + if (res != TEEC_SUCCESS) { + TEEC_CloseSession(&gTeeSess); + TEEC_FinalizeContext(&gTeeCtx); + return TPM_RC_FAILURE; + } + gRspShm.size = MAX_RESPONSE_SIZE; + if (gRspShm.size < taCmdSize) { + gRspShm.size = taCmdSize; + } + gRspShm.flags = TEEC_MEM_OUTPUT; + res = TEEC_AllocateSharedMemory(&gTeeCtx, &gRspShm); + if (res != TEEC_SUCCESS) { + TEEC_ReleaseSharedMemory(&gCmdShm); + TEEC_CloseSession(&gTeeSess); + TEEC_FinalizeContext(&gTeeCtx); + return TPM_RC_FAILURE; + } + + gTeeOpen = 1; + return TPM_RC_SUCCESS; +} + +int TPM2_OPTEE_SendCommand(TPM2_CTX* ctx, TPM2_Packet* packet) +{ + int rc; + TEEC_Operation op; + TEEC_Result res; + uint32_t origin = 0; + uint32_t rspSz; + + if (ctx == NULL || packet == NULL || packet->buf == NULL) { + return BAD_FUNC_ARG; + } + if (packet->pos < TPM2_HEADER_SIZE) { + return BAD_FUNC_ARG; + } + + /* Connect first: the shared memory is allocated there, so its size is + * not a bound to check against until it exists. */ + rc = OpteeConnect(); + if (rc != TPM_RC_SUCCESS) { + return rc; + } + if ((uint32_t)packet->pos > gCmdShm.size) { + return BAD_FUNC_ARG; + } + +#ifdef WOLFTPM_DEBUG_VERBOSE + printf("Command size: %d\n", packet->pos); + TPM2_PrintBin(packet->buf, packet->pos); +#endif + + XMEMCPY(gCmdShm.buffer, packet->buf, (size_t)packet->pos); + + XMEMSET(&op, 0, sizeof(op)); + op.paramTypes = TEEC_PARAM_TYPES(TEEC_MEMREF_PARTIAL_INPUT, + TEEC_MEMREF_PARTIAL_OUTPUT, + TEEC_NONE, TEEC_NONE); + op.params[0].memref.parent = &gCmdShm; + op.params[0].memref.offset = 0; + op.params[0].memref.size = (size_t)packet->pos; + op.params[1].memref.parent = &gRspShm; + op.params[1].memref.offset = 0; + op.params[1].memref.size = gRspShm.size; + + res = TEEC_InvokeCommand(&gTeeSess, FWTPM_OPTEE_CMD_SUBMIT, &op, &origin); + if (res != TEEC_SUCCESS) { + #ifdef DEBUG_WOLFTPM + printf("TEEC_InvokeCommand failed 0x%08x origin %u\n", + (unsigned int)res, (unsigned int)origin); + #endif + return TPM_RC_FAILURE; + } + + /* Trust the buffer size, not the length the response claims for itself. */ + rspSz = (uint32_t)op.params[1].memref.size; + if (rspSz < TPM2_HEADER_SIZE || rspSz > (uint32_t)packet->size || + rspSz > gRspShm.size) { + return TPM_RC_FAILURE; + } + XMEMCPY(packet->buf, gRspShm.buffer, (size_t)rspSz); + + packet->pos = 0; + packet->size = (int)rspSz; + +#ifdef WOLFTPM_DEBUG_VERBOSE + printf("Response size: %d\n", packet->size); + TPM2_PrintBin(packet->buf, (word32)packet->size); +#endif + + return TPM_RC_SUCCESS; +} + +void TPM2_OpteeClose(TPM2_CTX* ctx) +{ + (void)ctx; + OpteeDisconnect(); +} + +#endif /* WOLFTPM_OPTEE */ diff --git a/src/tpm2_spdm.c b/src/tpm2_spdm.c index b9c5e5322..905e3aa3b 100644 --- a/src/tpm2_spdm.c +++ b/src/tpm2_spdm.c @@ -44,7 +44,7 @@ /* TIS functions for SPI/I2C TPM transport */ #if defined(WOLFTPM_SPDM_TCG) && \ !defined(WOLFTPM_LINUX_DEV) && !defined(WOLFTPM_SWTPM) && \ - !defined(WOLFTPM_WINAPI) + !defined(WOLFTPM_OPTEE) && !defined(WOLFTPM_WINAPI) #include #include #define WOLFTPM_SPDM_TIS_IO diff --git a/src/tpm2_wrap.c b/src/tpm2_wrap.c index cbf0b7ea9..5a1c10430 100644 --- a/src/tpm2_wrap.c +++ b/src/tpm2_wrap.c @@ -129,7 +129,7 @@ static int wolfTPM2_Init_ex(TPM2_CTX* ctx, TPM2HalIoCb ioCb, void* userCtx, /* Fall through to Startup/SelfTest below */ #elif defined(WOLFTPM_LINUX_DEV) || defined(WOLFTPM_SWTPM) || \ - defined(WOLFTPM_WINAPI) + defined(WOLFTPM_OPTEE) || defined(WOLFTPM_WINAPI) rc = TPM2_Init_minimal(ctx); /* Using standard file I/O for the Linux TPM device */ (void)ioCb; @@ -12444,7 +12444,7 @@ static int tpm2_ifx_firmware_start(WOLFTPM2_DEV* dev, TPM_ALG_ID hashAlg, * since TPM reset into firmware upgrade mode */ #if !defined(WOLFTPM_LINUX_DEV) && !defined(WOLFTPM_SWTPM) && \ - !defined(WOLFTPM_WINAPI) + !defined(WOLFTPM_OPTEE) && !defined(WOLFTPM_WINAPI) /* Do chip startup and request locality again */ #ifdef WOLFTPM_LINUX_DEV_AUTODETECT if (dev->ctx.fd < 0) /* Only needed for SPI path */ @@ -12560,7 +12560,7 @@ static int tpm2_ifx_firmware_data(WOLFTPM2_DEV* dev, XSLEEP_MS(300); #if !defined(WOLFTPM_LINUX_DEV) && !defined(WOLFTPM_SWTPM) && \ - !defined(WOLFTPM_WINAPI) + !defined(WOLFTPM_OPTEE) && !defined(WOLFTPM_WINAPI) /* Do chip startup and request locality again */ #ifdef WOLFTPM_LINUX_DEV_AUTODETECT if (dev->ctx.fd < 0) /* Only needed for SPI path */ @@ -13090,7 +13090,7 @@ static int tpm2_st33_firmware_start_common(WOLFTPM2_DEV* dev, XSLEEP_MS(300); #if !defined(WOLFTPM_LINUX_DEV) && !defined(WOLFTPM_SWTPM) && \ - !defined(WOLFTPM_WINAPI) + !defined(WOLFTPM_OPTEE) && !defined(WOLFTPM_WINAPI) /* Do chip startup and request locality again */ #ifdef WOLFTPM_LINUX_DEV_AUTODETECT if (dev->ctx.fd < 0) /* Only needed for SPI path */ @@ -13215,7 +13215,7 @@ static int tpm2_st33_firmware_data(WOLFTPM2_DEV* dev, TPM_CC ccData, XSLEEP_MS(300); #if !defined(WOLFTPM_LINUX_DEV) && !defined(WOLFTPM_SWTPM) && \ - !defined(WOLFTPM_WINAPI) + !defined(WOLFTPM_OPTEE) && !defined(WOLFTPM_WINAPI) /* Do chip startup and request locality again */ #ifdef WOLFTPM_LINUX_DEV_AUTODETECT if (dev->ctx.fd < 0) /* Only needed for SPI path */ diff --git a/wolftpm/fwtpm/fwtpm.h b/wolftpm/fwtpm/fwtpm.h index acc06293a..9c7542764 100644 --- a/wolftpm/fwtpm/fwtpm.h +++ b/wolftpm/fwtpm/fwtpm.h @@ -742,8 +742,10 @@ struct FWTPM_NV_HAL_S { word32 maxSize; /* Total NV region size */ /* Override the NV-journal integrity key with a platform device secret * (e.g. hardware-fused or host-TPM backed). Return 0 and set *keySz on - * success. When NULL the default file backend uses an auto-created key - * file; integrity verification is always performed when a key exists. */ + * success. keySz is output only: it is zeroed before the call and carries + * no capacity, so do not test it. The buffer is 32 bytes. When NULL the + * default file backend uses an auto-created key file; integrity + * verification is always performed when a key exists. */ int (*get_integrity_key)(void* ctx, byte* key, word32* keySz); /* Program granule in bytes for append-only flash (0/1 = byte-writable). */ word32 writeAlign; diff --git a/wolftpm/fwtpm/fwtpm_nv.h b/wolftpm/fwtpm/fwtpm_nv.h index 766d78191..644f9301f 100644 --- a/wolftpm/fwtpm/fwtpm_nv.h +++ b/wolftpm/fwtpm/fwtpm_nv.h @@ -188,8 +188,9 @@ WOLFTPM_API int FWTPM_NV_Save(FWTPM_CTX* ctx); \return BAD_FUNC_ARG if ctx or hal is NULL \param ctx pointer to an initialized FWTPM_CTX - \param hal pointer to a caller-populated FWTPM_NV_HAL (the struct - must remain valid for the lifetime of the context). The caller MUST + \param hal pointer to a caller-populated FWTPM_NV_HAL. The struct is + copied, so it need not outlive this call, but anything hal.ctx points + at must remain valid for the lifetime of the context. The caller MUST zero-initialize the struct (e.g. XMEMSET(&hal, 0, sizeof(hal))) before populating fields: the appendOnly bit-field and reserved bits must be 0 unless append-only mode is intentionally enabled, otherwise diff --git a/wolftpm/fwtpm/fwtpm_optee.h b/wolftpm/fwtpm/fwtpm_optee.h new file mode 100644 index 000000000..1a2099715 --- /dev/null +++ b/wolftpm/fwtpm/fwtpm_optee.h @@ -0,0 +1,166 @@ +/* fwtpm_optee.h + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfTPM. + * + * wolfTPM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTPM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +/* fwTPM as an OP-TEE trusted application. Platform independent: anything + * with an OP-TEE secure world and the TEE Internal Core API can use it. */ + +#ifndef _FWTPM_OPTEE_H_ +#define _FWTPM_OPTEE_H_ + +/* Only an OP-TEE trusted application build has libutee, and WOLFSSL_OPTEE is + * what says so. WOLFTPM_FWTPM alone is not enough: a plain Linux fwTPM build + * defines that too, and an "every .c file" build would then reach for headers + * that are not there. */ +#if defined(WOLFTPM_FWTPM) && defined(WOLFSSL_OPTEE) + +#include +#include + +#include + +#endif /* WOLFTPM_FWTPM && WOLFSSL_OPTEE */ + +#ifdef __cplusplus + extern "C" { +#endif + +/* Command IDs and the trusted application's UUID. Shared with the + * normal-world client, so they stay outside WOLFTPM_FWTPM: a client holding + * only libteec can include this header and use them. A source that wants the + * secure-world declarations below must include first, + * since that is what defines WOLFTPM_FWTPM. */ + +/* Every OP-TEE trusted application is identified by a UUID rather than a + * name. It is not cosmetic and not per-build: the TEE loads a filesystem + * trusted application from /lib/optee_armtz/.ta, and a client opens a + * session by UUID, so the same value has to appear in three places that are + * built separately. + * + * 1. the trusted application's TA_UUID, via user_ta_header_defines.h + * 2. the signed binary's filename + * 3. the client, which is WOLFTPM_OPTEE_TA_UUID below + * + * This is the UUID wolfTPM's fwTPM trusted application uses, so a stock + * client and a stock trusted application interoperate with no configuration. + * A port that wants its own identity overrides these and must change all + * three together; a mismatch shows up only at run time, as + * TEEC_ERROR_ITEM_NOT_FOUND (0xffff0008) from TEEC_OpenSession. + * + * Derive every form from these rather than restating the digits. */ +#ifndef WOLFTPM_OPTEE_TA_UUID_STR +#define WOLFTPM_OPTEE_TA_UUID_STR "9013be0e-39b0-4ed4-80e0-1242c0daf382" +#define WOLFTPM_OPTEE_TA_UUID_P1 0x9013be0e +#define WOLFTPM_OPTEE_TA_UUID_P2 0x39b0 +#define WOLFTPM_OPTEE_TA_UUID_P3 0x4ed4 +#define WOLFTPM_OPTEE_TA_UUID_P4 { 0x80, 0xe0, 0x12, 0x42, \ + 0xc0, 0xda, 0xf3, 0x82 } +#endif + +/* params[0] memref-input: TPM 2.0 command packet + * params[1] memref-output: TPM 2.0 response packet, resized to the response + * Both buffers are normal-world memory, so the TA copies in and out. */ +#define FWTPM_OPTEE_CMD_SUBMIT 0 + +/* Flush transient objects, sessions and sequences. No parameters. The TA + * does this on session close as well; the command exists so a client can + * drop its own transient state without reconnecting. */ +#define FWTPM_OPTEE_CMD_RESET 1 + +/* params[0] value-output a: FWTPM_OPTEE_ABI_VERSION + * params[0] value-output b: FWTPM_MAX_COMMAND_SIZE of the built TA */ +#define FWTPM_OPTEE_CMD_GET_INFO 2 + +/* Bumped when the command IDs or their parameter layout change. */ +#define FWTPM_OPTEE_ABI_VERSION 1 + +/* The TA is the TPM, so the client passes no locality and the TA reports a + * fixed one. Locality is stored verbatim into the command context, so it must + * never come from the normal world. */ +#define FWTPM_OPTEE_LOCALITY 0 + +#if defined(WOLFTPM_FWTPM) && defined(WOLFSSL_OPTEE) + +/* Object ID of the NV journal in TEE_STORAGE_PRIVATE. Private storage is + * keyed to the TA UUID, so the name only has to be unique within the TA. */ +#ifndef FWTPM_OPTEE_NV_OBJECT_ID +#define FWTPM_OPTEE_NV_OBJECT_ID "wolftpm.fwtpm.nv" +#endif + +/* Object ID of the fallback integrity key, used only when the platform has + * no system PTA to derive a TA unique key from. */ +#ifndef FWTPM_OPTEE_KEY_OBJECT_ID +#define FWTPM_OPTEE_KEY_OBJECT_ID "wolftpm.fwtpm.nvkey" +#endif + +#ifndef FWTPM_OPTEE_NV_MAX_SIZE +#define FWTPM_OPTEE_NV_MAX_SIZE (64 * 1024) +#endif + +/* NV journal backed by OP-TEE secure storage. One of these must outlive the + * FWTPM_CTX it is registered with: FWTPM_NV_SetHAL copies the HAL struct but + * keeps the hal.ctx pointer, which points here. */ +typedef struct FWTPM_OPTEE_NV { + FWTPM_NV_HAL hal; + TEE_ObjectHandle obj; + word32 maxSize; +} FWTPM_OPTEE_NV; + +/*! + \brief Open (or create) the secure storage NV journal and register it as + the NV HAL for ctx. Must be called before FWTPM_Init, which is what runs + FWTPM_NV_Init. + + \return TPM_RC_SUCCESS on success + \return TPM_RC_FAILURE if secure storage is unavailable + \return BAD_FUNC_ARG if ctx or nv is NULL + + \param ctx pointer to a zeroed FWTPM_CTX + \param nv pointer to a zeroed FWTPM_OPTEE_NV that outlives ctx +*/ +int FWTPM_OPTEE_NV_SetHAL(FWTPM_CTX* ctx, FWTPM_OPTEE_NV* nv); + +/*! + \brief Close the secure storage object. Call after FWTPM_Cleanup, which + performs the final journal write. + + \param nv pointer to the FWTPM_OPTEE_NV passed to FWTPM_OPTEE_NV_SetHAL +*/ +void FWTPM_OPTEE_NV_Close(FWTPM_OPTEE_NV* nv); + +/*! + \brief Millisecond clock HAL backed by TEE_GetSystemTime, for + FWTPM_Clock_SetHAL. The TEE system time is monotonic across the TA + instance but has no relation to wall clock, which is what the TPM clock + needs. + + \return milliseconds since an arbitrary fixed point + + \param ctx unused +*/ +UINT64 FWTPM_OPTEE_Clock_GetMs(void* ctx); + +#endif /* WOLFTPM_FWTPM && WOLFSSL_OPTEE */ + +#ifdef __cplusplus + } /* extern "C" */ +#endif + +#endif /* _FWTPM_OPTEE_H_ */ diff --git a/wolftpm/include.am b/wolftpm/include.am index 713eea2ca..9c63f6882 100644 --- a/wolftpm/include.am +++ b/wolftpm/include.am @@ -10,6 +10,8 @@ nobase_include_HEADERS+= \ wolftpm/tpm2_wrap.h \ wolftpm/tpm2_linux.h \ wolftpm/tpm2_swtpm.h \ + wolftpm/tpm2_optee.h \ + wolftpm/fwtpm/fwtpm_optee.h \ wolftpm/tpm2_winapi.h \ wolftpm/tpm2_param_enc.h \ wolftpm/tpm2_crypto.h \ diff --git a/wolftpm/tpm2_optee.h b/wolftpm/tpm2_optee.h new file mode 100644 index 000000000..044e763f0 --- /dev/null +++ b/wolftpm/tpm2_optee.h @@ -0,0 +1,63 @@ +/* tpm2_optee.h + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfTPM. + * + * wolfTPM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTPM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +#ifndef _TPM2_OPTEE_H_ +#define _TPM2_OPTEE_H_ + +#ifdef WOLFTPM_OPTEE + +#include +#include +#include + +#ifdef __cplusplus + extern "C" { +#endif + +/* TEEC_UUID initializer for the trusted application this client opens, built + * from the single definition in fwtpm_optee.h. See the note there before + * overriding it: the same UUID has to appear in the trusted application and + * in its filename too. */ +#ifndef WOLFTPM_OPTEE_TA_UUID +#define WOLFTPM_OPTEE_TA_UUID \ + { WOLFTPM_OPTEE_TA_UUID_P1, \ + WOLFTPM_OPTEE_TA_UUID_P2, \ + WOLFTPM_OPTEE_TA_UUID_P3, \ + WOLFTPM_OPTEE_TA_UUID_P4 } +#endif + +/* Send one whole TPM command to the trusted application and read back the + * response. One TEEC_InvokeCommand maps onto one FWTPM_ProcessCommand, so no + * TIS register emulation and one world switch per command rather than per + * register access. */ +WOLFTPM_LOCAL int TPM2_OPTEE_SendCommand(TPM2_CTX* ctx, TPM2_Packet* packet); + +/* Release the TEE session, context and shared memory during final TPM + * context cleanup. Idempotent. */ +WOLFTPM_LOCAL void TPM2_OpteeClose(TPM2_CTX* ctx); + +#ifdef __cplusplus + } /* extern "C" */ +#endif + +#endif /* WOLFTPM_OPTEE */ + +#endif /* _TPM2_OPTEE_H_ */ diff --git a/wolftpm/tpm2_types.h b/wolftpm/tpm2_types.h index 67464969f..85af20bee 100644 --- a/wolftpm/tpm2_types.h +++ b/wolftpm/tpm2_types.h @@ -301,9 +301,19 @@ typedef int64_t INT64; #ifndef WOLFTPM_CUSTOM_TYPES #ifndef WOLFTPM2_NO_HEAP + #ifdef WOLFSSL_OPTEE + /* An OP-TEE trusted application links libutee rather than a C library and + * has no malloc. wolfCrypt maps these itself, but this is the branch where + * it is not compiled in. WOLFTPM2_NO_HEAP still wins: it leaves both + * undefined, so an allocation that slipped in fails to compile. */ + #include + #define XMALLOC(s, h, t) TEE_Malloc((size_t)(s), TEE_MALLOC_FILL_ZERO) + #define XFREE(p, h, t) TEE_Free(p) + #else #define XMALLOC(s, h, t) malloc((size_t)(s)) #define XFREE(p, h, t) free(p) #endif + #endif #define XMEMCPY(d,s,l) memcpy((d),(s),(l)) #define XMEMMOVE(d,s,l) memmove((d),(s),(l)) #define XMEMSET(b,c,l) memset((b),(c),(l)) @@ -544,6 +554,7 @@ typedef int64_t INT64; /* On Linux with autodetect, also try /dev/tpm0 kernel driver at runtime */ #if defined(WOLFTPM_AUTODETECT) && defined(__linux__) && \ !defined(WOLFTPM_LINUX_DEV) && !defined(WOLFTPM_SWTPM) && \ + !defined(WOLFTPM_OPTEE) && \ !defined(WOLFTPM_WINAPI) && !defined(WOLFTPM_FWTPM_HAL) #define WOLFTPM_LINUX_DEV_AUTODETECT #endif @@ -635,7 +646,8 @@ typedef int64_t INT64; #endif #ifndef TPM_TIMEOUT_TRIES - #if defined(WOLFTPM_LINUX_DEV) || defined(WOLFTPM_SWTPM) || defined(WOLFTPM_WINAPI) + #if defined(WOLFTPM_LINUX_DEV) || defined(WOLFTPM_SWTPM) || \ + defined(WOLFTPM_OPTEE) || defined(WOLFTPM_WINAPI) #define TPM_TIMEOUT_TRIES 0 #else #define TPM_TIMEOUT_TRIES 1000000 @@ -782,6 +794,21 @@ typedef int64_t INT64; #include #define XTPM_GET_TIMEMS() ((word32)GetTickCount64()) #define WOLFTPM_HAVE_MONOTONIC_MS + #elif defined(WOLFSSL_OPTEE) + /* An OP-TEE trusted application has no CLOCK_MONOTONIC, so the POSIX + * branch below silently leaves this unset and timeouts fall back to a + * counter. TEE_GetSystemTime is monotonic for the life of the trusted + * application. Deliberately not TEE_GetREETime: the normal world owns + * that clock and can move it backwards, which would make a command + * timeout hang or fire early. Works with or without wolfCrypt. */ + #include + static inline word32 XTPM_GET_TIMEMS(void) + { + TEE_Time now; + TEE_GetSystemTime(&now); + return (word32)((word32)now.seconds * 1000u + (word32)now.millis); + } + #define WOLFTPM_HAVE_MONOTONIC_MS #else /* Include first, then feature-test: strict C99 may not expose * CLOCK_MONOTONIC, and __linux__ alone would fail to compile. */