diff --git a/NVIDIA/fwtpm-jetson-optee/.gitignore b/NVIDIA/fwtpm-jetson-optee/.gitignore new file mode 100644 index 0000000..37d6ec8 --- /dev/null +++ b/NVIDIA/fwtpm-jetson-optee/.gitignore @@ -0,0 +1,23 @@ +# Created by ta/prep.sh +ta/wc/ +ta/wt/ +ta/port/ +ta/inc-cfg +ta/inc-wolfssl +ta/inc-wolftpm +ta/uuid.mk +ta/uuid_macro.inc + +# OP-TEE TA build output +ta/*.ta +ta/*.elf +ta/*.dmp +ta/*.map +ta/*.o +ta/.*.cmd +ta/.*.d +ta/dyn_list +ta/ta.lds + +native/fwtpm_native +host/fwtpm_client diff --git a/NVIDIA/fwtpm-jetson-optee/README.md b/NVIDIA/fwtpm-jetson-optee/README.md new file mode 100644 index 0000000..538adc6 --- /dev/null +++ b/NVIDIA/fwtpm-jetson-optee/README.md @@ -0,0 +1,331 @@ +# fwTPM on the NVIDIA Jetson Orin, as an OP-TEE trusted application + +The wolfTPM firmware TPM 2.0 running in the secure world on a Jetson Orin, as +an OP-TEE trusted application, with unmodified wolfTPM examples driving it +from Linux over `libteec`. + +The Jetson already has a firmware TPM: NVIDIA ships Microsoft's TPM 2.0 +reference as an early trusted application, reached through `tpm_ftpm_tee` as +`/dev/tpmrm0`. It reports **TPM 2.0 spec revision 1.62**, and ML-DSA +(FIPS 204) and ML-KEM (FIPS 203) arrive in revision **1.85**, so it has no +post-quantum support until NVIDIA ships new firmware. This port puts a 1.85 +TPM on the same silicon alongside it. + +## Tested target + +| Board | Status | Date | +|-------|--------|------| +| Jetson Orin Nano Engineering Reference Developer Kit Super, L4T R36.4.4, kernel 5.15.148-tegra | `Mfg WOLF (0), Vendor wolfTPM`, `TPM_PT_REVISION` 185. `caps`, `getrandom`, `native_test`, `wrap_test`, `pqc/mldsa_sign`, `pqc/mlkem_encap` and both PQC negative-control examples pass. NV persists across processes. | 2026-10-28 | + +## Prerequisites + +- `aarch64-linux-gnu-gcc`. Nothing else is needed on the build host. +- An OP-TEE TA devkit (`export-ta_arm64`), optee_os 4.4.0 verified. The arm64 + TA devkit targets the TEE Internal Core API, so the platform it was built + for does not matter for an ordinary trusted application. +- wolfTPM with `WOLFTPM_OPTEE` support, which supplies + `src/fwtpm/ports/optee/` and `wolftpm/fwtpm/fwtpm_optee.h`. +- wolfSSL with ML-DSA and ML-KEM (`--enable-mldsa --enable-mlkem + --enable-experimental`), and with the `WOLFSSL_OPTEE` heap and RNG support, + which supplies the allocator and entropy so this port needs no shims. Only + the sources are used for the trusted application; the install is for the + Linux-side examples. +- On the board, `nvidia-l4t-optee`, which ships `tee_client_api.h` and + `libteec`, so the client builds natively there. + +`/dev/tee0` and `/dev/teepriv0` are mode `0600` and root owned, so **every +client runs under sudo**. + +## Signing + +A filesystem-loaded trusted application has to be signed with a key the +platform's OP-TEE core trusts. Check what your image uses, against a trusted +application it already ships, before building anything: + +```sh +python3 scripts/sign_encrypt.py verify \ + --key keys/default_ta.pem \ + --uuid 5b9e0e40-2636-11e1-ad9e-0002a5d5c51b \ + --in 5b9e0e40-2636-11e1-ad9e-0002a5d5c51b.ta +``` + +Exit 0 means that key. **Run the negative control too**, or exit 0 tells you +nothing: repeat with a freshly generated key (`openssl genrsa -out wrong.pem +2048`) and confirm it reports a verification failure. + +On L4T R36.4.4 the shipped trusted applications verify against upstream +OP-TEE's `keys/default_ta.pem`, so the devkit's default key works unchanged. + +## Build and run + +```sh +export WOLFSSL=../../../../wolfssl +export WOLFTPM=../../../../wolftpm +export TA_DEV_KIT_DIR=/path/to/optee_os/out/.../export-ta_arm64 +``` + +### 1. Build and sign the trusted application + +```sh +cd ta && WOLFSSL=$WOLFSSL WOLFTPM=$WOLFTPM ./prep.sh +make TA_DEV_KIT_DIR=$TA_DEV_KIT_DIR CROSS_COMPILE=aarch64-linux-gnu- +``` + +`prep.sh` symlinks the wolfCrypt, fwTPM and OP-TEE port sources plus the three +include roots into the build directory, and generates `uuid.mk` and +`uuid_macro.inc` from wolfTPM. The OP-TEE build resolves every `srcs-y` and +`global-incdirs-y` entry against the `sub.mk` directory, so nothing outside it +can be named directly. Re-run `prep.sh` after moving either tree. Signing is +automatic and uses the devkit key. + +Configuration is one `user_settings.h` for both wolfCrypt and wolfTPM, reached +through `WOLFSSL_USER_SETTINGS` and `WOLFTPM_USER_SETTINGS`. There is no +generated `options.h` here, and that is also what keeps an installed +`/usr/local/include/wolftpm/options.h` out of the build. + +### 2. Deploy + +```sh +scp 9013be0e-39b0-4ed4-80e0-1242c0daf382.ta board:/tmp/ +ssh board "sudo install -m 0644 -o root -g root \ + /tmp/9013be0e-39b0-4ed4-80e0-1242c0daf382.ta /lib/optee_armtz/" +``` + +No service restart: `tee-supplicant` reads `/lib/optee_armtz/` per request. + +**Before installing, run the client once and confirm it fails with +`TEEC_OpenSession 0xffff0008` (`TEEC_ERROR_ITEM_NOT_FOUND`), origin 3.** +Otherwise a later success may be a stale trusted application rather than the +one you just built. + +### 3. Drive it + +The minimal `libteec` client, built on the board: + +```sh +make -C host && sudo ./host/fwtpm_client +``` + +Or unmodified wolfTPM examples, which is the point of the port: + +```sh +cd wolftpm +./configure --enable-optee --disable-swtpm --enable-v185 \ + CFLAGS="-I$WOLFSSL_PREFIX/include" LDFLAGS="-L$WOLFSSL_PREFIX/lib" +make +sudo ./examples/wrap/caps +sudo ./examples/pqc/mldsa_sign +``` + +`--disable-swtpm` is required: the software TPM is the default on Linux +aarch64 and would win. Build the client **without** `--enable-fwtpm`; the +trusted application is the server. + +## The trusted application's UUID + +OP-TEE identifies a trusted application by UUID, not by name, and the UUID is +not cosmetic: 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, and a +mismatch shows up only at run time as `TEEC_OpenSession` returning +`0xffff0008` (`TEEC_ERROR_ITEM_NOT_FOUND`). + +There is one definition, `WOLFTPM_OPTEE_TA_UUID_STR` in wolfTPM's +`wolftpm/fwtpm/fwtpm_optee.h`, and everything here derives from it: + +| Where | How it gets the UUID | +|-------|----------------------| +| `TA_UUID` in the trusted application | `ta/uuid_macro.inc`, generated by `prep.sh` | +| the signed binary's filename | `BINARY` in `ta/Makefile`, from `ta/uuid.mk`, generated by `prep.sh` | +| the client | `WOLFTPM_OPTEE_TA_UUID` in `wolftpm/tpm2_optee.h`, built from the same macros | + +`ta/uuid.mk` and `ta/uuid_macro.inc` are generated, not checked in. This is +the UUID wolfTPM's fwTPM trusted application uses, so a stock client and a +stock trusted application interoperate with no configuration. To ship your own +identity, override `WOLFTPM_OPTEE_TA_UUID_STR` and the `_P1` to `_P4` macros +beside it; all three places then follow. + +## Developing or porting this + +`native/` compiles the same source list with the same `user_settings.h` for +the host and drives `FWTPM_ProcessCommand` through a RAM NV HAL: + +```sh +make -C native && ./native/fwtpm_native +``` + +Nothing about running the fwTPM on a board needs it. It is for changing the +port: a configuration or source-list mistake surfaces in seconds there +instead of after a cross-compile, copy and deploy cycle, and it runs under a +host debugger and the sanitizers. + +## How it works + +`FWTPM_ProcessCommand` takes a command buffer and returns a response buffer, +which is exactly the shape of `TA_InvokeCommandEntryPoint`, so one +`TEEC_InvokeCommand` carries one whole TPM command. There is no TIS register +emulation, which would cost a world switch per register access. + +`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. + +NV lives in OP-TEE secure storage in the journal's default byte-addressable +mode; the append-only option is for write-once flash and is not used here. The +journal integrity key is derived from the hardware unique key through the +system PTA, falling back to a random key in its own storage object on +platforms built without that PTA. + +`locality` is fixed and never taken from the client, because it is stored +verbatim into the command context without validation. + +## Sizes + +Measured on this board, bounded from both sides, acceptance being +`native_test` plus `pqc/mldsa_sign`: + +| Axis | Passes | Fails | +|------|--------|-------| +| `TA_DATA_SIZE` (heap) | 96 KB | 88 KB | +| `TA_STACK_SIZE` | 8 KB | 4 KB | + +ML-DSA is the binding constraint: at 64 KB of heap `native_test` still passes +and only ML-DSA fails. The acceptance test signs with ML-DSA-65, so ML-DSA-87 +needs more. + +`FWTPM_CTX` is a 161 KB static in `.bss` and does not come out of this heap, +which is why a 96 KB heap works at all. The trusted application load ceiling +on this board is between 47 and 48 MB. + +Shipping defaults are 32 KB of stack and 512 KB of heap. `sweep.sh` re-measures +on another board; sweep until something fails, or you have not found a floor. + +The heap and stack floors are the same with the assembly in or out, because +ML-DSA binds the heap and the single-precision code allocates its temporaries +rather than using stack. + +### Assembly, and what it costs + +Both are on by default on aarch64 and each has an opt-out, so a target that +wants a smaller image or lacks the instructions can drop either: + +| Build | `.text` | `.rodata` | TA image | +|-------|---------|-----------|----------| +| `FWTPM_TA_NO_SP_ASM` and `FWTPM_TA_NO_ARMASM` | 480624 | 34241 | 604872 | +| `FWTPM_TA_NO_ARMASM` only | 586772 | 530361 | 1215176 | +| default, both enabled | 641120 | 532841 | 1272600 | + +The single-precision assembly is what costs the space, and nearly all of it is +`.rodata`: the ECC precomputed base-point tables, plus RSA 4096 and P-384 +support. It buys RSA-2048 private 10.9x and ECDSA P-256 sign 46x. The ARMv8 +crypto extensions add 4.7% for ML-KEM encapsulate +32% and decapsulate +48%, +ECC P-256 key generation +38%, AES +15% and SHA-256 +8%. + +ML-DSA gains nothing measurable from either: there is no aarch64 ML-DSA +polynomial assembly, and Keccak is not where its time goes. + +`FWTPM_TA_NO_ARMASM` also drops the `-march=armv8.2-a+crypto` requirement in +`ta/sub.mk`, which exists because the ML-KEM transform uses SQRDMLSH from +FEAT_RDM and the devkit names no architecture. + +## Benchmarks + +`examples/bench/bench`, same tool and board as the vendor fTPM figures, second +run kept so the first pays the trusted application load and the frequency ramp. + +| Operation | This port | NVIDIA vendor fTPM | +|-----------|----------:|-------------------:| +| RSA-2048 key gen | 1043 ms | 736 ms | +| RSA-2048 public | 3038 ops/sec | 1144 ops/sec | +| RSA-2048 private | 56.8 ops/sec | 84 ops/sec | +| ECDSA P-256 sign | 204 ops/sec | 22.2 ops/sec | +| ECDSA P-256 verify | 316 ops/sec | 31.5 ops/sec | +| ECDHE P-256 agree | 104 ops/sec | 11.2 ops/sec | +| AES-256-CBC | 15.6 MB/s | 2.9 MB/s | +| SHA-256 | 7.79 MB/s | 1.57 MB/s | +| ML-DSA-65 sign | 1012 ops/sec | not supported | +| ML-DSA-65 verify | 2072 ops/sec | not supported | +| ML-KEM-768 encapsulate | 6956 ops/sec | not supported | +| ML-KEM-768 decapsulate | 4684 ops/sec | not supported | +| SHA-1 | not supported | supported | +| AES-CTR | not supported | supported | + +Faster than the vendor part on ECC, AES and SHA-256, **slower on RSA private +and RSA key generation**, and it does not implement SHA-1 or AES-CTR where the +vendor fTPM skips nothing. + +**`bench` key generation rows for ML-DSA and ML-KEM are not key generation.** +The loop calls `wolfTPM2_CreatePrimaryKey` on an unchanging template and the +fwTPM has a four-entry primary cache, so every iteration after the first is a +cache hit. `bench -iter=100` shows it: ML-DSA-65 key gen min 0.247 ms, max +72.8 ms, while ECC P-256 key gen stays within 5.065-5.150 ms because it uses +`TPM2_Create` and is not cached. A first, uncached ML-DSA-65 `CreatePrimary` +costs roughly 73-90 ms, part of which is the NV journal write to secure +storage. + +## Reaching it as /dev/tpm1, with standard Linux tooling + +The libteec client above is not the only way in. Linux's `tpm_ftpm_tee` driver +binds a TPM chip to a trusted application, so a copy of it carrying this +UUID presents the fwTPM as `/dev/tpm1` and `/dev/tpmrm1`, alongside the +vendor part on `/dev/tpm0`. `tpm2-tools` and anything else speaking the +kernel TPM interface then work unmodified. + +The driver is GPL-2.0 and is not vendored here. `kernel/tpm_wolftpm_tee.patch` +is a diff against `drivers/char/tpm/tpm_ftpm_tee.c` from Linux v5.15. Build it +out of tree on the board, which has the kernel headers: + +```sh +mkdir -p ~/wolftpm-drv && cd ~/wolftpm-drv +# from a Linux v5.15 source tree, or fetch the three files directly +cp /drivers/char/tpm/tpm_ftpm_tee.c tpm_wolftpm_tee.c +cp /drivers/char/tpm/tpm_ftpm_tee.h tpm.h . +patch -p1 < /kernel/tpm_wolftpm_tee.patch +printf 'obj-m += tpm_wolftpm_tee.o\n' > Makefile +make -C /lib/modules/$(uname -r)/build M=$PWD modules +sudo insmod ./tpm_wolftpm_tee.ko +``` + +The patch does three things: points both UUID sites at this trusted +application, renames the driver and module so they coexist with the vendor +driver rather than fighting over its device tree node, and registers the +platform device itself, since this trusted application has no device tree +node of its own. + +What works through the kernel path, confirmed with `tpm2-tools`: + +| Command | `/dev/tpmrm1` | `/dev/tpm1` | +|---------|---------------|-------------| +| `tpm2_getcap properties-fixed` | pass, reports `WOLF` at revision 1.85 | pass | +| `tpm2_getrandom`, `tpm2_pcrread`, `tpm2_selftest` | pass | pass | +| `tpm2_createprimary` | **fails** | pass | + +Creating a transient object through the resource manager fails because +`tpm2-space` flushes the handle afterwards and the fwTPM answers +`TPM_RC_HANDLE` (139). The raw device has no resource manager and is +unaffected. That is fwTPM behaviour rather than anything about OP-TEE, so the +raw device is the working path until it is addressed. + +Two traps if you try this: + +- Do not add `TA_FLAG_DEVICE_ENUM_TEE_STORAGE_PRIVATE` to get onto the Linux + TEE bus. The devkit defines it but the OP-TEE core in L4T R36.4.4 is older + and rejects the unknown flag, after which the trusted application stops + loading at all and every session returns `TEEC_ERROR_BAD_FORMAT`. +- The vendor fTPM does not reach Linux over the TEE bus either. It is a + platform device from a `microsoft,ftpm` device tree node, which is why the + patch registers a platform device rather than relying on enumeration. + +## Limits + +- The trusted application has its own UUID and **cannot appear as + `/dev/tpm0`**. The vendor fTPM owns that UUID as an early trusted + application compiled into `tos.img`, and an early one always beats a + filesystem one. Use the `WOLFTPM_OPTEE` transport, not `tpm_ftpm_tee`. +- The vendor fTPM keeps working throughout; the two are independent TPMs on + one SoC with separate state. +- A firmware TPM shares the SoC with the software it attests. Where the threat + model includes physical possession, a discrete part remains a different + proposition. +- Single instance by design: one instance is one TPM, since a second would be + a second TPM sharing one NV journal. diff --git a/NVIDIA/fwtpm-jetson-optee/host/Makefile b/NVIDIA/fwtpm-jetson-optee/host/Makefile new file mode 100644 index 0000000..e25d0d5 --- /dev/null +++ b/NVIDIA/fwtpm-jetson-optee/host/Makefile @@ -0,0 +1,16 @@ +# Normal-world client, built on the board: nvidia-l4t-optee ships +# tee_client_api.h and libteec there, so no cross toolchain is needed. +# +# The shared command IDs come from the wolfTPM tree. +WOLFTPM ?= ../../../../wolftpm + +CFLAGS += -Wall -Wextra -O2 -I$(WOLFTPM) +LDLIBS += -lteec + +fwtpm_client: main.c + $(CC) $(CFLAGS) -o $@ main.c $(LDLIBS) + +clean: + rm -f fwtpm_client + +.PHONY: clean diff --git a/NVIDIA/fwtpm-jetson-optee/host/main.c b/NVIDIA/fwtpm-jetson-optee/host/main.c new file mode 100644 index 0000000..39bcb9c --- /dev/null +++ b/NVIDIA/fwtpm-jetson-optee/host/main.c @@ -0,0 +1,237 @@ +/* Normal-world client for the fwTPM trusted application. + * + * Minimal libteec driver used to bring the TA up on the board. The real + * client is the WOLFTPM_OPTEE transport in wolfTPM, which lets unmodified + * wolfTPM examples drive the same TA. + * + * Build and run on the board: + * make && sudo ./fwtpm_client + */ + +#include +#include +#include +#include +#include + +#include + +static const TEEC_UUID taUuid = { + 0x9013be0e, 0x39b0, 0x4ed4, + { 0x80, 0xe0, 0x12, 0x42, 0xc0, 0xda, 0xf3, 0x82 } +}; + +static TEEC_Context ctx; +static TEEC_Session sess; +static int fails; + +static unsigned int BeU32(const unsigned char* p) +{ + return ((unsigned int)p[0] << 24) | ((unsigned int)p[1] << 16) | + ((unsigned int)p[2] << 8) | (unsigned int)p[3]; +} + +/* Returns the TPM response code, or 0xFFFFFFFF if the invoke itself failed. */ +static unsigned int Submit(const char* name, const unsigned char* cmd, + size_t cmdSz, unsigned char* rsp, size_t rspCap, size_t* rspSz) +{ + TEEC_Operation op; + TEEC_Result res; + uint32_t origin = 0; + unsigned int rc; + + memset(&op, 0, sizeof(op)); + op.paramTypes = TEEC_PARAM_TYPES(TEEC_MEMREF_TEMP_INPUT, + TEEC_MEMREF_TEMP_OUTPUT, + TEEC_NONE, TEEC_NONE); + op.params[0].tmpref.buffer = (void*)cmd; + op.params[0].tmpref.size = cmdSz; + op.params[1].tmpref.buffer = rsp; + op.params[1].tmpref.size = rspCap; + + res = TEEC_InvokeCommand(&sess, FWTPM_OPTEE_CMD_SUBMIT, &op, &origin); + if (res != TEEC_SUCCESS) { + printf("%-28s TEEC 0x%08x origin %u\n", name, res, origin); + *rspSz = 0; + return 0xFFFFFFFFu; + } + *rspSz = op.params[1].tmpref.size; + if (*rspSz < 10) { + printf("%-28s short response %zu\n", name, *rspSz); + return 0xFFFFFFFFu; + } + rc = BeU32(rsp + 6); + printf("%-28s rsp %3zu bytes, TPM_RC 0x%08X %s\n", name, *rspSz, rc, + (rc == 0) ? "OK" : "FAIL"); + return rc; +} + +static void Expect(const char* name, const unsigned char* cmd, size_t cmdSz) +{ + unsigned char rsp[8192]; + size_t rspSz = 0; + + if (Submit(name, cmd, cmdSz, rsp, sizeof(rsp), &rspSz) != 0) { + fails++; + } +} + +static const unsigned char cmdStartup[] = { + 0x80, 0x01, 0x00, 0x00, 0x00, 0x0C, + 0x00, 0x00, 0x01, 0x44, + 0x00, 0x00 +}; +static const unsigned char cmdSelfTest[] = { + 0x80, 0x01, 0x00, 0x00, 0x00, 0x0B, + 0x00, 0x00, 0x01, 0x43, + 0x00 +}; +static const unsigned char cmdGetRandom[] = { + 0x80, 0x01, 0x00, 0x00, 0x00, 0x0C, + 0x00, 0x00, 0x01, 0x7B, + 0x00, 0x20 +}; +/* TPM_CAP_TPM_PROPERTIES, TPM_PT_MANUFACTURER, one property */ +static const unsigned char cmdGetCapMfg[] = { + 0x80, 0x01, 0x00, 0x00, 0x00, 0x16, + 0x00, 0x00, 0x01, 0x7A, + 0x00, 0x00, 0x00, 0x06, + 0x00, 0x00, 0x01, 0x05, + 0x00, 0x00, 0x00, 0x01 +}; +/* TPM_PT_REVISION: the reason the vendor fTPM has no post-quantum commands */ +static const unsigned char cmdGetCapRev[] = { + 0x80, 0x01, 0x00, 0x00, 0x00, 0x16, + 0x00, 0x00, 0x01, 0x7A, + 0x00, 0x00, 0x00, 0x06, + 0x00, 0x00, 0x01, 0x02, + 0x00, 0x00, 0x00, 0x01 +}; + +int main(void) +{ + TEEC_Operation op; + TEEC_Result res; + unsigned char rsp[8192]; + unsigned char bad[12]; + size_t rspSz = 0; + uint32_t origin = 0; + unsigned int rc; + unsigned int i; + + res = TEEC_InitializeContext(NULL, &ctx); + if (res != TEEC_SUCCESS) { + errx(1, "TEEC_InitializeContext 0x%08x", res); + } + res = TEEC_OpenSession(&ctx, &sess, &taUuid, TEEC_LOGIN_PUBLIC, NULL, + NULL, &origin); + if (res != TEEC_SUCCESS) { + TEEC_FinalizeContext(&ctx); + errx(1, "TEEC_OpenSession 0x%08x origin %u", res, origin); + } + printf("session open\n"); + + memset(&op, 0, sizeof(op)); + op.paramTypes = TEEC_PARAM_TYPES(TEEC_VALUE_OUTPUT, TEEC_NONE, + TEEC_NONE, TEEC_NONE); + res = TEEC_InvokeCommand(&sess, FWTPM_OPTEE_CMD_GET_INFO, &op, &origin); + if (res != TEEC_SUCCESS) { + printf("GET_INFO failed 0x%08x origin %u\n", res, origin); + fails++; + } + else { + printf("ABI version %u, max command size %u\n", + op.params[0].value.a, op.params[0].value.b); + if (op.params[0].value.a != FWTPM_OPTEE_ABI_VERSION) { + printf(" ABI mismatch, client built for %u\n", + FWTPM_OPTEE_ABI_VERSION); + fails++; + } + } + + Expect("TPM2_Startup", cmdStartup, sizeof(cmdStartup)); + Expect("TPM2_SelfTest", cmdSelfTest, sizeof(cmdSelfTest)); + + rc = Submit("TPM2_GetCapability(mfg)", cmdGetCapMfg, sizeof(cmdGetCapMfg), + rsp, sizeof(rsp), &rspSz); + if (rc != 0) { + fails++; + } + else if (rspSz >= 27) { + /* tag(2) size(4) rc(4) more(1) cap(4) count(4) property(4) value(4) */ + printf(" TPM_PT_MANUFACTURER = 0x%08X \"%.4s\"\n", + BeU32(rsp + 23), (const char*)(rsp + 23)); + } + + rc = Submit("TPM2_GetCapability(rev)", cmdGetCapRev, sizeof(cmdGetCapRev), + rsp, sizeof(rsp), &rspSz); + if (rc != 0) { + fails++; + } + else if (rspSz >= 27) { + printf(" TPM_PT_REVISION = %u (spec %u.%02u)\n", + BeU32(rsp + 23), BeU32(rsp + 23) / 100, BeU32(rsp + 23) % 100); + } + + rc = Submit("TPM2_GetRandom", cmdGetRandom, sizeof(cmdGetRandom), + rsp, sizeof(rsp), &rspSz); + if (rc != 0) { + fails++; + } + else if (rspSz >= 44) { + printf(" 32 random bytes:"); + for (i = 0; i < 8; i++) { + printf(" %02x", rsp[12 + i]); + } + printf(" ...\n"); + } + + /* Negative control: an unimplemented command code must be refused, or a + * TPM that answered everything with success would look identical. */ + memcpy(bad, cmdGetRandom, sizeof(bad)); + bad[8] = 0xFF; + bad[9] = 0xFF; + rc = Submit("bogus command code", bad, sizeof(bad), rsp, sizeof(rsp), + &rspSz); + if (rc == 0) { + printf(" negative control FAILED: bogus command accepted\n"); + fails++; + } + else { + printf(" negative control OK: refused with 0x%08X\n", rc); + } + + /* Negative control: a short output buffer must report the size needed + * rather than truncate the response. */ + memset(&op, 0, sizeof(op)); + op.paramTypes = TEEC_PARAM_TYPES(TEEC_MEMREF_TEMP_INPUT, + TEEC_MEMREF_TEMP_OUTPUT, + TEEC_NONE, TEEC_NONE); + op.params[0].tmpref.buffer = (void*)cmdGetRandom; + op.params[0].tmpref.size = sizeof(cmdGetRandom); + op.params[1].tmpref.buffer = rsp; + op.params[1].tmpref.size = 4; + res = TEEC_InvokeCommand(&sess, FWTPM_OPTEE_CMD_SUBMIT, &op, &origin); + if (res == TEEC_ERROR_SHORT_BUFFER) { + printf("%-28s OK: needs %zu bytes\n", "short output buffer", + op.params[1].tmpref.size); + } + else { + printf("%-28s FAILED: 0x%08x\n", "short output buffer", res); + fails++; + } + + res = TEEC_InvokeCommand(&sess, FWTPM_OPTEE_CMD_RESET, NULL, &origin); + printf("%-28s %s\n", "CMD_RESET", + (res == TEEC_SUCCESS) ? "OK" : "FAIL"); + if (res != TEEC_SUCCESS) { + fails++; + } + + TEEC_CloseSession(&sess); + TEEC_FinalizeContext(&ctx); + + printf("\n%s\n", (fails == 0) ? "fwTPM TA checks PASSED" + : "fwTPM TA checks FAILED"); + return (fails == 0) ? 0 : 1; +} diff --git a/NVIDIA/fwtpm-jetson-optee/kernel/tpm_wolftpm_tee.patch b/NVIDIA/fwtpm-jetson-optee/kernel/tpm_wolftpm_tee.patch new file mode 100644 index 0000000..8d6d1bf --- /dev/null +++ b/NVIDIA/fwtpm-jetson-optee/kernel/tpm_wolftpm_tee.patch @@ -0,0 +1,81 @@ +--- a/drivers/char/tpm/tpm_ftpm_tee.c ++++ b/drivers/char/tpm/tpm_wolftpm_tee.c +@@ -28,8 +28,8 @@ + * https://github.com/microsoft/ms-tpm-20-ref/blob/master/Samples/ARM32-FirmwareTPM/optee_ta/fTPM/include/fTPM.h#L42 + */ + static const uuid_t ftpm_ta_uuid = +- UUID_INIT(0xBC50D971, 0xD4C9, 0x42C4, +- 0x82, 0xCB, 0x34, 0x3F, 0xB7, 0xF3, 0x78, 0x96); ++ UUID_INIT(0x9013BE0E, 0x39B0, 0x4ED4, ++ 0x80, 0xE0, 0x12, 0x42, 0xC0, 0xDA, 0xF3, 0x82); + + /** + * ftpm_tee_tpm_op_recv() - retrieve fTPM response. +@@ -355,14 +355,14 @@ + } + + static const struct of_device_id of_ftpm_tee_ids[] = { +- { .compatible = "microsoft,ftpm" }, ++ { .compatible = "wolfssl,wolftpm" }, + { } + }; + MODULE_DEVICE_TABLE(of, of_ftpm_tee_ids); + + static struct platform_driver ftpm_tee_plat_driver = { + .driver = { +- .name = "ftpm-tee", ++ .name = "wolftpm-tee", + .of_match_table = of_match_ptr(of_ftpm_tee_ids), + }, + .shutdown = ftpm_plat_tee_shutdown, +@@ -372,8 +372,8 @@ + + /* UUID of the fTPM TA */ + static const struct tee_client_device_id optee_ftpm_id_table[] = { +- {UUID_INIT(0xbc50d971, 0xd4c9, 0x42c4, +- 0x82, 0xcb, 0x34, 0x3f, 0xb7, 0xf3, 0x78, 0x96)}, ++ {UUID_INIT(0x9013be0e, 0x39b0, 0x4ed4, ++ 0x80, 0xe0, 0x12, 0x42, 0xc0, 0xda, 0xf3, 0x82)}, + {} + }; + +@@ -382,13 +382,15 @@ + static struct tee_client_driver ftpm_tee_driver = { + .id_table = optee_ftpm_id_table, + .driver = { +- .name = "optee-ftpm", ++ .name = "optee-wolftpm", + .bus = &tee_bus_type, + .probe = ftpm_tee_probe, + .remove = ftpm_tee_remove, + }, + }; + ++static struct platform_device *wolftpm_pdev; ++ + static int __init ftpm_mod_init(void) + { + int rc; +@@ -397,11 +399,22 @@ + if (rc) + return rc; + ++ /* Bench variant: this trusted application has no device tree node, so ++ * create the platform device the driver binds to. */ ++ wolftpm_pdev = platform_device_register_simple("wolftpm-tee", -1, ++ NULL, 0); ++ if (IS_ERR(wolftpm_pdev)) { ++ platform_driver_unregister(&ftpm_tee_plat_driver); ++ return PTR_ERR(wolftpm_pdev); ++ } ++ + return driver_register(&ftpm_tee_driver.driver); + } + + static void __exit ftpm_mod_exit(void) + { ++ if (!IS_ERR_OR_NULL(wolftpm_pdev)) ++ platform_device_unregister(wolftpm_pdev); + platform_driver_unregister(&ftpm_tee_plat_driver); + driver_unregister(&ftpm_tee_driver.driver); + } diff --git a/NVIDIA/fwtpm-jetson-optee/native/Makefile b/NVIDIA/fwtpm-jetson-optee/native/Makefile new file mode 100644 index 0000000..dc448bd --- /dev/null +++ b/NVIDIA/fwtpm-jetson-optee/native/Makefile @@ -0,0 +1,26 @@ +# Native dry run: the same source list and user_settings.h as the trusted +# application, built for the host so source-list and configuration problems +# surface on a fast loop instead of a cross-compile, scp and sudo cycle. + +WOLFSSL ?= ../../../../wolfssl +WOLFTPM ?= ../../../../wolftpm + +include ../srcs.mk + +CC ?= gcc +CFLAGS += -O1 -g -Wall -Wextra -Wno-unused-parameter +CFLAGS += -DWOLFSSL_USER_SETTINGS -DWOLFTPM_USER_SETTINGS -DFWTPM_TA_NATIVE +CFLAGS += -I. -I.. -I$(WOLFSSL) -I$(WOLFTPM) + +SRCS = main.c $(addprefix $(WOLFSSL)/,$(WC_SRCS)) \ + $(addprefix $(WOLFTPM)/,$(FWTPM_SRCS)) + +all: fwtpm_native + +fwtpm_native: $(SRCS) + $(CC) $(CFLAGS) -o $@ $(SRCS) + +clean: + rm -f fwtpm_native + +.PHONY: all clean diff --git a/NVIDIA/fwtpm-jetson-optee/native/main.c b/NVIDIA/fwtpm-jetson-optee/native/main.c new file mode 100644 index 0000000..b923d81 --- /dev/null +++ b/NVIDIA/fwtpm-jetson-optee/native/main.c @@ -0,0 +1,207 @@ +/* Native dry run for the fwTPM-in-OP-TEE port. + * + * Builds the same wolfCrypt and fwTPM source list, with the same + * user_settings.h, that the trusted application uses, and drives + * FWTPM_ProcessCommand through a RAM NV HAL. Its only job is to find source + * list, configuration and NV HAL problems off-board. + */ + +#include +#include +#include +#include + +#include +#include +#include + +/* The heap needs no shim: this host build does not define WOLFSSL_OPTEE, so + * wolfCrypt uses its normal allocator, and the trusted application gets + * TEE_Malloc from wolfSSL. Only the RNG needs standing in for, because + * NO_FILESYSTEM closes the host seed path. */ + +int fwtpm_native_rand(unsigned char* out, unsigned int sz) +{ + FILE* f; + size_t rd; + + f = fopen("/dev/urandom", "rb"); + if (f == NULL) { + return -1; + } + rd = fread(out, 1, sz, f); + fclose(f); + return (rd == (size_t)sz) ? 0 : -1; +} + +/* --- RAM NV HAL, standing in for OP-TEE secure storage --- */ + +#define NV_SIZE (64 * 1024) +static unsigned char nvRam[NV_SIZE]; + +static int NvRead(void* ctx, word32 offset, byte* buf, word32 size) +{ + (void)ctx; + if ((word32)offset + size > NV_SIZE) { + return -1; + } + memcpy(buf, &nvRam[offset], size); + return 0; +} + +static int NvWrite(void* ctx, word32 offset, const byte* buf, word32 size) +{ + (void)ctx; + if ((word32)offset + size > NV_SIZE) { + return -1; + } + memcpy(&nvRam[offset], buf, size); + return 0; +} + +static int NvErase(void* ctx, word32 offset, word32 size) +{ + (void)ctx; + if ((word32)offset + size > NV_SIZE) { + return -1; + } + memset(&nvRam[offset], 0xFF, size); + return 0; +} + +static int NvIntegrityKey(void* ctx, byte* key, word32* keySz) +{ + word32 i; + + (void)ctx; + if (*keySz < 32) { + return -1; + } + /* Fixed key: the dry run only needs the journal MAC to be consistent. + * The trusted application derives this from the hardware unique key. */ + for (i = 0; i < 32; i++) { + key[i] = (byte)(0x5A ^ i); + } + *keySz = 32; + return 0; +} + +/* --- Command helpers --- */ + +static FWTPM_CTX fwCtx; +static byte rspBuf[FWTPM_MAX_COMMAND_SIZE]; + +static int RunCmd(const char* name, const byte* cmd, int cmdSize) +{ + int rc; + int rspSize = (int)sizeof(rspBuf); + word32 tpmRc = 0xFFFFFFFF; + + rc = FWTPM_ProcessCommand(&fwCtx, cmd, cmdSize, rspBuf, &rspSize, 0); + if (rc != 0) { + printf("%-28s transport rc %d\n", name, rc); + return -1; + } + if (rspSize < 10) { + printf("%-28s short response %d\n", name, rspSize); + return -1; + } + tpmRc = ((word32)rspBuf[6] << 24) | ((word32)rspBuf[7] << 16) | + ((word32)rspBuf[8] << 8) | (word32)rspBuf[9]; + printf("%-28s rsp %3d bytes, TPM_RC 0x%08X %s\n", name, rspSize, + (unsigned int)tpmRc, (tpmRc == 0) ? "OK" : "FAIL"); + return (tpmRc == 0) ? 0 : -1; +} + +static const byte cmdStartup[] = { + 0x80, 0x01, /* TPM_ST_NO_SESSIONS */ + 0x00, 0x00, 0x00, 0x0C, /* size 12 */ + 0x00, 0x00, 0x01, 0x44, /* TPM_CC_Startup */ + 0x00, 0x00 /* TPM_SU_CLEAR */ +}; + +static const byte cmdGetRandom[] = { + 0x80, 0x01, + 0x00, 0x00, 0x00, 0x0C, /* size 12 */ + 0x00, 0x00, 0x01, 0x7B, /* TPM_CC_GetRandom */ + 0x00, 0x20 /* 32 bytes requested */ +}; + +static const byte cmdGetCapability[] = { + 0x80, 0x01, + 0x00, 0x00, 0x00, 0x16, /* size 22 */ + 0x00, 0x00, 0x01, 0x7A, /* TPM_CC_GetCapability */ + 0x00, 0x00, 0x00, 0x06, /* TPM_CAP_TPM_PROPERTIES */ + 0x00, 0x00, 0x01, 0x05, /* TPM_PT_MANUFACTURER */ + 0x00, 0x00, 0x00, 0x01 /* propertyCount 1 */ +}; + +static const byte cmdSelfTest[] = { + 0x80, 0x01, + 0x00, 0x00, 0x00, 0x0B, /* size 11 */ + 0x00, 0x00, 0x01, 0x43, /* TPM_CC_SelfTest */ + 0x00 /* fullTest = NO */ +}; + +int main(void) +{ + FWTPM_NV_HAL nvHal; + int rc; + int fails = 0; + + memset(nvRam, 0xFF, sizeof(nvRam)); + memset(&fwCtx, 0, sizeof(fwCtx)); + memset(&nvHal, 0, sizeof(nvHal)); + + nvHal.read = NvRead; + nvHal.write = NvWrite; + nvHal.erase = NvErase; + nvHal.get_integrity_key = NvIntegrityKey; + nvHal.maxSize = NV_SIZE; + nvHal.ctx = NULL; + + rc = FWTPM_NV_SetHAL(&fwCtx, &nvHal); + if (rc != 0) { + printf("FWTPM_NV_SetHAL failed %d\n", rc); + return 1; + } + + rc = FWTPM_Init(&fwCtx); + if (rc != 0) { + printf("FWTPM_Init failed %d\n", rc); + return 1; + } + printf("FWTPM_Init OK, sizeof(FWTPM_CTX) = %u bytes\n", + (unsigned int)sizeof(FWTPM_CTX)); + + fails += (RunCmd("TPM2_Startup", cmdStartup, + (int)sizeof(cmdStartup)) != 0); + fails += (RunCmd("TPM2_SelfTest", cmdSelfTest, + (int)sizeof(cmdSelfTest)) != 0); + fails += (RunCmd("TPM2_GetCapability", cmdGetCapability, + (int)sizeof(cmdGetCapability)) != 0); + fails += (RunCmd("TPM2_GetRandom", cmdGetRandom, + (int)sizeof(cmdGetRandom)) != 0); + + /* Negative control: a command code the fwTPM does not implement must be + * refused rather than silently succeed. */ + { + byte bad[12]; + memcpy(bad, cmdGetRandom, sizeof(bad)); + bad[8] = 0xFF; + bad[9] = 0xFF; + if (RunCmd("bogus command code", bad, (int)sizeof(bad)) == 0) { + printf(" negative control FAILED: bogus command accepted\n"); + fails++; + } + else { + printf(" negative control OK: bogus command refused\n"); + } + } + + FWTPM_Cleanup(&fwCtx); + + printf("\n%s\n", (fails == 0) ? "native dry run PASSED" + : "native dry run FAILED"); + return (fails == 0) ? 0 : 1; +} diff --git a/NVIDIA/fwtpm-jetson-optee/srcs.mk b/NVIDIA/fwtpm-jetson-optee/srcs.mk new file mode 100644 index 0000000..e0400e6 --- /dev/null +++ b/NVIDIA/fwtpm-jetson-optee/srcs.mk @@ -0,0 +1,67 @@ +# Source lists shared by the native dry run and the OP-TEE trusted application. +# Paths are relative to the wolfssl and wolftpm trees respectively. + +# wolfCrypt surface the fwTPM command set reaches: RSA and ECC (with key +# generation), AES-CFB, HMAC/HKDF, SHA-1/2/3, ASN.1 encode and decode, and +# ML-DSA / ML-KEM. +WC_SRCS = \ + wolfcrypt/src/aes.c \ + wolfcrypt/src/asn.c \ + wolfcrypt/src/coding.c \ + wolfcrypt/src/cpuid.c \ + wolfcrypt/src/ecc.c \ + wolfcrypt/src/error.c \ + wolfcrypt/src/hash.c \ + wolfcrypt/src/hmac.c \ + wolfcrypt/src/kdf.c \ + wolfcrypt/src/logging.c \ + wolfcrypt/src/memory.c \ + wolfcrypt/src/random.c \ + wolfcrypt/src/rsa.c \ + wolfcrypt/src/sha.c \ + wolfcrypt/src/sha256.c \ + wolfcrypt/src/sha3.c \ + wolfcrypt/src/sha512.c \ + wolfcrypt/src/sp_int.c \ + wolfcrypt/src/wc_encrypt.c \ + wolfcrypt/src/wc_mldsa.c \ + wolfcrypt/src/wc_mlkem.c \ + wolfcrypt/src/wc_mlkem_poly.c \ + wolfcrypt/src/wc_port.c \ + wolfcrypt/src/wolfmath.c + +# Trusted application only: the aarch64 assembly. The native dry run builds +# for x86, so it takes WC_SRCS alone. Each file self-gates on the macros +# user_settings.h defines only for __aarch64__, so none of this reaches a +# build for another architecture. +# +# sp_arm64.c is the single-precision RSA and ECC. The armv8-* files are the +# ARMv8 crypto extensions for AES and SHA-2, the ML-KEM NTT and the Keccak +# permutation, in their inline-assembly form (WOLFSSL_ARMASM_INLINE), which +# avoids handing .S files to the OP-TEE build. +# +# armv8-sha3-asm_c.c holds both BlockSha3_crypto, which needs FEAT_SHA3, and +# BlockSha3_base, which does not. Only the latter is built here, because +# user_settings.h leaves WOLFSSL_ARMASM_CRYPTO_SHA3 undefined, and it is what +# accelerates SHAKE and so ML-DSA and ML-KEM. +WC_SRCS_TA = \ + wolfcrypt/src/sp_arm64.c \ + wolfcrypt/src/port/arm/armv8-aes-asm_c.c \ + wolfcrypt/src/port/arm/armv8-sha256-asm_c.c \ + wolfcrypt/src/port/arm/armv8-sha512-asm_c.c \ + wolfcrypt/src/port/arm/armv8-sha3-asm_c.c \ + wolfcrypt/src/port/arm/armv8-mlkem-asm_c.c + +# The fwTPM minus every transport. fwtpm_io.c, fwtpm_tis.c, fwtpm_tis_shm.c +# and fwtpm_main.c are deliberately absent: both server loops block, which a +# trusted application cannot do, and TA_InvokeCommandEntryPoint is the +# transport instead. +FWTPM_SRCS = \ + src/fwtpm/fwtpm.c \ + src/fwtpm/fwtpm_command.c \ + src/fwtpm/fwtpm_crypto.c \ + src/fwtpm/fwtpm_nv.c \ + src/tpm2_util.c \ + src/tpm2_packet.c \ + src/tpm2_crypto.c \ + src/tpm2_param_enc.c diff --git a/NVIDIA/fwtpm-jetson-optee/sweep.sh b/NVIDIA/fwtpm-jetson-optee/sweep.sh new file mode 100755 index 0000000..2f096fc --- /dev/null +++ b/NVIDIA/fwtpm-jetson-optee/sweep.sh @@ -0,0 +1,79 @@ +#!/bin/bash +# Find the TA_DATA_SIZE and TA_STACK_SIZE floors for the fwTPM trusted +# application on a target board. +# +# Acceptance per size is native_test (RSA create/load/encrypt/decrypt, NV, +# AES-CFB) plus pqc/mldsa_sign, which between them exercise the heaviest +# allocation paths. A size passes only if both do. +# +# Always sweep until something FAILS. A sweep where every size passes has +# not measured a floor, it has only shown that the harness runs. +# +# BOARD=orin@10.0.4.244 TA_DEV_KIT_DIR=... ./sweep.sh [stack_kb] [heap_kb ...] +# +# Environment: +# BOARD ssh destination for the target (required) +# SUDO_PW board sudo password; /dev/tee0 is root only +# TA_DEV_KIT_DIR OP-TEE TA devkit (export-ta_arm64) +# CROSS_COMPILE default aarch64-linux-gnu- +# REMOTE_WOLFTPM wolfTPM build on the board that holds the examples +# WOLFSSL_PREFIX wolfSSL install the examples link against +set -u + +HERE="$(cd "$(dirname "$0")" && pwd)" +UUID=9013be0e-39b0-4ed4-80e0-1242c0daf382 + +BOARD="${BOARD:?set BOARD to the target ssh destination}" +TA_DEV_KIT_DIR="${TA_DEV_KIT_DIR:?set TA_DEV_KIT_DIR to the OP-TEE TA devkit}" +CROSS_COMPILE="${CROSS_COMPILE:-aarch64-linux-gnu-}" +REMOTE_WOLFTPM="${REMOTE_WOLFTPM:-\$HOME/wolftpm-optee}" +WOLFSSL_PREFIX="${WOLFSSL_PREFIX:-/usr/local}" +SUDO_PW="${SUDO_PW:-}" + +if [ -n "$SUDO_PW" ]; then + SUDO="echo $SUDO_PW | sudo -S -p ''" +else + SUDO="sudo" +fi + +STACK_KB="${1:-32}" +shift || true +HEAPS="${*:-512 256 128 96 88 64}" + +echo "stack ${STACK_KB} KB; heap sizes: $HEAPS" +echo + +for kb in $HEAPS; do + cat > "$HERE/ta/ta_sizes.h" </dev/null 2>&1; then + printf '%6s KB BUILD FAILED\n' "$kb" + continue + fi + scp -q "$HERE/ta/$UUID.ta" "$BOARD:/tmp/" || { + printf '%6s KB SCP FAILED\n' "$kb"; continue; } + ssh "$BOARD" "$SUDO install -m 0644 -o root -g root \ + /tmp/$UUID.ta /lib/optee_armtz/$UUID.ta" >/dev/null 2>&1 + + out=$(ssh "$BOARD" "cd $REMOTE_WOLFTPM && \ + LL=$WOLFSSL_PREFIX/lib:\$PWD/src/.libs; \ + $SUDO env LD_LIBRARY_PATH=\$LL ./examples/native/native_test \ + >/dev/null 2>&1; echo nt=\$?; \ + $SUDO env LD_LIBRARY_PATH=\$LL ./examples/pqc/mldsa_sign \ + >/dev/null 2>&1; echo ml=\$?" 2>&1) + nt=$(echo "$out" | sed -n 's/^nt=//p') + ml=$(echo "$out" | sed -n 's/^ml=//p') + + if [ "$nt" = "0" ] && [ "$ml" = "0" ]; then + printf '%6s KB PASS\n' "$kb" + else + printf '%6s KB FAIL (native_test=%s mldsa_sign=%s)\n' "$kb" "$nt" "$ml" + fi +done + +echo +echo "Restore ta/ta_sizes.h to the shipping defaults when done." diff --git a/NVIDIA/fwtpm-jetson-optee/ta/Makefile b/NVIDIA/fwtpm-jetson-optee/ta/Makefile new file mode 100644 index 0000000..99ac870 --- /dev/null +++ b/NVIDIA/fwtpm-jetson-optee/ta/Makefile @@ -0,0 +1,9 @@ +CFG_TEE_TA_LOG_LEVEL ?= 2 + +# Generated by prep.sh from WOLFTPM_OPTEE_TA_UUID_STR in wolfTPM. The TEE +# loads a filesystem trusted application from /lib/optee_armtz/.ta, so +# the binary name is the UUID, not a choice. +include uuid.mk +BINARY = $(TA_UUID_STR) + +-include $(TA_DEV_KIT_DIR)/mk/ta_dev_kit.mk diff --git a/NVIDIA/fwtpm-jetson-optee/ta/prep.sh b/NVIDIA/fwtpm-jetson-optee/ta/prep.sh new file mode 100755 index 0000000..ca0641d --- /dev/null +++ b/NVIDIA/fwtpm-jetson-optee/ta/prep.sh @@ -0,0 +1,96 @@ +#!/bin/bash +# Symlink the wolfCrypt, fwTPM and OP-TEE port sources into the TA tree, and +# the three include roots alongside them. +# +# The OP-TEE TA build resolves every srcs-y and global-incdirs-y entry against +# the sub.mk directory, so nothing outside it can be named directly. Symlinks +# keep one copy of the truth and let the wolfSSL and wolfTPM trees live +# anywhere. An earlier version of this port copied the sources instead, and a +# stale copy once made a failed build look like a successful one. +# +# WOLFSSL= WOLFTPM= ./prep.sh +set -eu + +here="$(cd "$(dirname "$0")" && pwd)" +port="$(cd "$here/.." && pwd)" + +WOLFSSL="${WOLFSSL:-$port/../../../wolfssl}" +WOLFTPM="${WOLFTPM:-$port/../../../wolftpm}" +wolfssl="$(cd "$WOLFSSL" && pwd)" +wolftpm="$(cd "$WOLFTPM" && pwd)" + +test -f "$wolfssl/wolfcrypt/src/wc_port.c" || { + echo "not a wolfSSL tree: $wolfssl" >&2; exit 1; } +# A configured wolfSSL tree has a generated config.h at its root, and this +# build puts that root on the include path, so it shadows the OP-TEE devkit's +# own . The failure lands inside the devkit's user_ta_header.c and +# looks nothing like its cause, so refuse it here instead. +test -f "$wolfssl/config.h" && { + echo "$wolfssl has a generated config.h and would shadow the OP-TEE" >&2 + echo "devkit's . Use an unconfigured wolfSSL source tree, or" >&2 + echo "run 'make distclean' in that one first." >&2 + exit 1; } + +test -d "$wolftpm/src/fwtpm/ports/optee" || { + echo "wolfTPM tree has no OP-TEE port: $wolftpm" >&2 + echo "needs the WOLFTPM_OPTEE support in wolfTPM v4.2.0 or later" >&2 + exit 1; } + +# Pull the two lists out of the shared srcs.mk rather than restating them. +wc_srcs=$(grep -E '^[[:space:]]+wolfcrypt/src/([a-z0-9_/-]+/)?[a-z0-9_-]+\.c' \ + "$port/srcs.mk" | tr -d ' \t\\') +fwtpm_srcs=$(grep -E '^[[:space:]]+src/[a-z0-9_/]+\.c' "$port/srcs.mk" \ + | tr -d ' \t\\') + +rm -rf "$here/wc" "$here/wt" "$here/port" +rm -f "$here/inc-cfg" "$here/inc-wolfssl" "$here/inc-wolftpm" +mkdir -p "$here/wc" "$here/wt" "$here/port" + +for s in $wc_srcs; do + test -f "$wolfssl/$s" || { echo "missing $wolfssl/$s" >&2; exit 1; } + ln -s "$wolfssl/$s" "$here/wc/$(basename "$s")" +done +for s in $fwtpm_srcs; do + test -f "$wolftpm/$s" || { echo "missing $wolftpm/$s" >&2; exit 1; } + ln -s "$wolftpm/$s" "$here/wt/$(basename "$s")" +done +# Only the sources: the shared command-ID header is reached through the +# wolfTPM include path as . +for s in "$wolftpm"/src/fwtpm/ports/optee/*.c; do + ln -s "$s" "$here/port/$(basename "$s")" +done + +# Include roots. inc-cfg holds user_settings.h. +ln -s "$port" "$here/inc-cfg" +ln -s "$wolfssl" "$here/inc-wolfssl" +ln -s "$wolftpm" "$here/inc-wolftpm" + +# The trusted application's UUID has one definition, in wolfTPM, and both the +# signed binary's filename and TA_UUID are derived from it here. See the note +# on WOLFTPM_OPTEE_TA_UUID_STR in wolftpm/fwtpm/fwtpm_optee.h for why the same +# value has to appear in three separately built places. +uuid=$(sed -n 's/^#define WOLFTPM_OPTEE_TA_UUID_STR *"\(.*\)"$/\1/p' \ + "$wolftpm/wolftpm/fwtpm/fwtpm_optee.h") +case "$uuid" in + [0-9a-f]???????-????-????-????-???????????? ) ;; + *) echo "could not read WOLFTPM_OPTEE_TA_UUID_STR from $wolftpm" >&2 + exit 1 ;; +esac + +printf 'TA_UUID_STR = %s\n' "$uuid" > "$here/uuid.mk" +python3 -I -c ' +import sys +u = sys.argv[1].replace("-", "") +b = [u[i:i+2] for i in range(0, 32, 2)] +print("/* Generated by prep.sh from WOLFTPM_OPTEE_TA_UUID_STR. Do not edit. */") +print("#define TA_UUID \\") +print(" { 0x%s, 0x%s, 0x%s, \\" % (u[0:8], u[8:12], u[12:16])) +print(" { " + ", ".join("0x" + x for x in b[8:]) + " } }") +' "$uuid" > "$here/uuid_macro.inc" +echo "UUID: $uuid (from wolfTPM)" + +echo "wolfSSL: $wolfssl" +echo "wolfTPM: $wolftpm" +echo "wc: $(ls "$here/wc" | wc -l) wolfCrypt sources" +echo "wt: $(ls "$here/wt" | wc -l) fwTPM sources" +echo "port: $(ls "$here/port" | wc -l) OP-TEE port sources" diff --git a/NVIDIA/fwtpm-jetson-optee/ta/sub.mk b/NVIDIA/fwtpm-jetson-optee/ta/sub.mk new file mode 100644 index 0000000..dce00c3 --- /dev/null +++ b/NVIDIA/fwtpm-jetson-optee/ta/sub.mk @@ -0,0 +1,42 @@ +include $(sub-dir)/../srcs.mk + +# All four are symlinks created by prep.sh. The OP-TEE TA build resolves these +# against this directory, so the real trees cannot be named here. +# +# inc-cfg is this port directory and holds user_settings.h. +global-incdirs-y += inc-cfg +global-incdirs-y += inc-wolfssl +global-incdirs-y += inc-wolftpm +global-incdirs-y += port + +# One user_settings.h configures both, so neither falls back to a generated +# options.h. Without WOLFTPM_USER_SETTINGS, wolfTPM would include +# and silently pick up an installed one. +cflags-y += -DWOLFSSL_USER_SETTINGS +cflags-y += -DWOLFTPM_USER_SETTINGS +# Required: the TA toolchain is a Linux cross compiler and predefines +# __unix__, so wolfCrypt has to be told what it is really building for. +cflags-y += -DWOLFSSL_OPTEE + +# The devkit names no architecture, so the compiler defaults to plain armv8-a +# and the assembler rejects the AES and SHA-2 crypto extensions and the +# SQRDMLSH the ML-KEM NTT uses, which came in FEAT_RDM. The Cortex-A78AE on a +# Jetson Orin is ARMv8.2-A and has all of them. A target without them wants +# this line removed and WOLFSSL_ARMASM dropped from user_settings.h. +cflags-y += -march=armv8.2-a+crypto + +# The devkit warning set is stricter than either upstream tree builds with. +cflags-y += -Wno-unused-parameter -Wno-declaration-after-statement +cflags-y += -Wno-switch-default -Wno-redundant-decls -Wno-undef +cflags-y += -Wno-missing-declarations -Wno-missing-prototypes +cflags-y += -Wno-shadow -Wno-cast-align -Wno-float-equal + +# OP-TEE trusted application entry points and the secure storage NV HAL, from +# wolfTPM: TEE Internal Core API only, so nothing in them is specific to this +# board. The wolfCrypt heap and RNG mapping comes from wolfSSL itself. +srcs-y += port/fwtpm_optee_ta.c +srcs-y += port/fwtpm_optee_nv.c + +srcs-y += $(addprefix wt/,$(notdir $(FWTPM_SRCS))) +srcs-y += $(addprefix wc/,$(notdir $(WC_SRCS))) +srcs-y += $(addprefix wc/,$(notdir $(WC_SRCS_TA))) diff --git a/NVIDIA/fwtpm-jetson-optee/ta/ta_sizes.h b/NVIDIA/fwtpm-jetson-optee/ta/ta_sizes.h new file mode 100644 index 0000000..2c7276d --- /dev/null +++ b/NVIDIA/fwtpm-jetson-optee/ta/ta_sizes.h @@ -0,0 +1,15 @@ +/* Measured on this board, bounded from both sides. + * + * heap (TA_DATA_SIZE): 96 KB passes, 88 KB fails. At 64 KB native_test + * still passes and only ML-DSA fails, so ML-DSA is + * the binding constraint. The acceptance test signs + * with ML-DSA-65; ML-DSA-87 needs more. + * stack (TA_STACK_SIZE): 8 KB passes, 4 KB fails. WOLFTPM_SMALL_STACK and + * WOLFSSL_SMALL_STACK keep the large temporaries on + * the heap, which is what makes that possible. + * + * FWTPM_CTX is a 161 KB static and lives in .bss, not in this heap. + * The TA load ceiling on this board is between 47 and 48 MB, so these + * defaults leave roughly 5x headroom on the heap and 4x on the stack. */ +#define TA_STACK_SIZE (32 * 1024) +#define TA_DATA_SIZE (512 * 1024) diff --git a/NVIDIA/fwtpm-jetson-optee/ta/user_ta_header_defines.h b/NVIDIA/fwtpm-jetson-optee/ta/user_ta_header_defines.h new file mode 100644 index 0000000..c7d42c3 --- /dev/null +++ b/NVIDIA/fwtpm-jetson-optee/ta/user_ta_header_defines.h @@ -0,0 +1,23 @@ +#ifndef USER_TA_HEADER_DEFINES_H +#define USER_TA_HEADER_DEFINES_H + +#include "uuid_macro.inc" +#include "ta_sizes.h" + +/* Single instance: one trusted application instance is one TPM, since a + * second would be a second TPM sharing one NV journal. Multi session so more + * than one normal-world client can hold a session, as the kernel resource + * manager does for a discrete part. + * + * Do not add TA_FLAG_DEVICE_ENUM_TEE_STORAGE_PRIVATE here to get onto the + * Linux TEE bus. The devkit defines it, but the OP-TEE core shipped in L4T + * R36.4.4 is older and rejects the unknown flag bit, so the trusted + * application stops loading at all and every session open returns + * TEEC_ERROR_BAD_FORMAT. The devkit being newer than the core is the trap. */ +#define TA_FLAGS (TA_FLAG_EXEC_DDR | TA_FLAG_SINGLE_INSTANCE | \ + TA_FLAG_MULTI_SESSION) + +#define TA_CURRENT_TA_EXT_PROPERTIES \ + { "gp.ta.description", USER_TA_PROP_TYPE_STRING, "wolfTPM fwTPM" } + +#endif /* USER_TA_HEADER_DEFINES_H */ diff --git a/NVIDIA/fwtpm-jetson-optee/user_settings.h b/NVIDIA/fwtpm-jetson-optee/user_settings.h new file mode 100644 index 0000000..981c4d1 --- /dev/null +++ b/NVIDIA/fwtpm-jetson-optee/user_settings.h @@ -0,0 +1,183 @@ +#ifndef FWTPM_TA_USER_SETTINGS_H +#define FWTPM_TA_USER_SETTINGS_H + +/* Reached through both WOLFSSL_USER_SETTINGS and WOLFTPM_USER_SETTINGS, which + * both include "user_settings.h", so this one file configures wolfCrypt and + * wolfTPM together. That is also what keeps an installed + * /usr/local/include/wolftpm/options.h out of the build: wolfTPM only falls + * back to when WOLFTPM_USER_SETTINGS is undefined. */ + +/* --- wolfTPM --- */ + +/* Build the fwTPM itself, not a client of one. */ +#define WOLFTPM_FWTPM + +/* TPM 2.0 Library Specification v1.85: the ML-DSA and ML-KEM commands. */ +#define WOLFTPM_V185 + +/* TA_STACK_SIZE is small while the TA_DATA_SIZE heap is generous. */ +#define WOLFTPM_SMALL_STACK + +/* --- wolfCrypt --- */ + +/* wolfCrypt configuration for the wolfTPM fwTPM running as an OP-TEE trusted + * application. A TA links libutee rather than a C library: no filesystem, + * sockets, threads or wall clock. + * + * The same header is used for the native x86 dry run so the source list and + * the algorithm set are validated off-board. FWTPM_TA_NATIVE selects the + * host platform shims instead of the TEE ones. */ + +#define WOLFCRYPT_ONLY +#define SINGLE_THREADED +#define NO_FILESYSTEM +#define NO_STDIO_FILESYSTEM +#define NO_WRITEV +#define WOLFSSL_NO_SOCK +#define NO_WOLFSSL_DIR +#define NO_MAIN_DRIVER +#define NO_ASN_TIME +#define USE_WOLF_STRTOK + +/* libutee has no strcasecmp, strncasecmp or strncat, so use wolfCrypt's own + * implementations. ECC curve lookup by name is the only caller that matters + * here. */ +#define USE_WOLF_STRCASECMP +#define USE_WOLF_STRNCASECMP + +/* Big-number math: single implementation covering RSA, ECC and keygen. */ +#define WOLFSSL_SP_MATH_ALL + +/* Optimized single-precision RSA and ECC. sp_arm64.c carries the aarch64 + * assembly for RSA 2048/3072/4096 and ECC P-256/P-384, which is most of the + * private-key cost in a TPM; WOLFSSL_SP_MATH_ALL above stays as the fallback + * for sizes it does not cover. Guarded on the architecture because the native + * dry run builds these same sources for x86. */ +#if defined(__aarch64__) && !defined(FWTPM_TA_NO_SP_ASM) + #define WOLFSSL_HAVE_SP_RSA + #define WOLFSSL_HAVE_SP_ECC + #define WOLFSSL_SP_ARM64 + #define WOLFSSL_SP_ARM64_ASM + #define WOLFSSL_SP_4096 + #define WOLFSSL_SP_384 +#endif + +/* ARMv8 crypto extensions for AES and SHA-2, the ML-KEM number-theoretic + * transform and the Keccak permutation, in the inline-assembly form so the + * OP-TEE build never sees a .S file. Build with FWTPM_TA_NO_ARMASM to leave + * them out. + * + * The Cortex-A78AE reports aes, pmull, sha1 and sha2, so the compiled-in + * feature flags this selects are correct for it. A core without those + * extensions needs WOLFSSL_ARMASM_NO_HW_CRYPTO as well, because a trusted + * application cannot detect them at run time. + * + * Neither FEAT_SHA3 nor FEAT_SHA512 is implemented on this core, so the + * instruction paths needing them stay off. Keccak still gets assembly: + * BlockSha3_base in armv8-sha3-asm_c.c sits outside the + * WOLFSSL_ARMASM_CRYPTO_SHA3 guard and requires no extension. */ +#if defined(__aarch64__) && !defined(FWTPM_TA_NO_ARMASM) + #define WOLFSSL_ARMASM + #define WOLFSSL_ARMASM_INLINE +#endif +/* Deliberately not WOLFSSL_AARCH64_BUILD: it defines HAVE_CPUID_AARCH64 + * (cpuid.h), whose Linux arm gets runtime feature detection from + * . The TA toolchain predefines __linux__ but libutee has no + * such header. sp_arm64.c needs only WOLFSSL_SP_ARM64_ASM, and a trusted + * application has no use for CPU feature dispatch. */ + +/* The TPM generates its own primary keys. */ +#define WOLFSSL_KEY_GEN + +/* WOLFSSL_KEY_GEN otherwise pulls in wc_DerToPemEx, whose only use of + * XSTRNCAT reaches a strncat that libutee does not provide. A TPM has no + * reason to emit PEM. */ +#define WOLFSSL_NO_DER_TO_PEM + +/* TPM 2.0 RSA signing uses PKCS#1 v1.5 and PSS. The fwTPM also performs raw + * RSA, which without WC_RSA_NO_PADDING returns TPM_RC_SCHEME: wolfTPM's + * configure warns about this, and a trusted application build has no + * configure step to warn it. */ +#define WC_RSA_PSS +#define WC_RSA_NO_PADDING + +/* Side-channel hardening. A TPM holds long-lived keys, so this is not + * optional here even though the TA is the only thing in its address space. */ +#define WC_RSA_BLINDING +#define TFM_TIMING_RESISTANT +#define ECC_TIMING_RESISTANT + +/* Classical algorithms the fwTPM command set requires. */ +#define HAVE_ECC +#define ECC_USER_CURVES +#define HAVE_ECC256 +#define HAVE_ECC384 +#define ECC_SHAMIR +#define HAVE_ECC_SIGN +#define HAVE_ECC_VERIFY +#define HAVE_ECC_DHE +#define HAVE_ECC_KEY_EXPORT +#define HAVE_ECC_KEY_IMPORT +#define WOLFSSL_CUSTOM_CURVES +#define WOLFSSL_PUBLIC_MP + +#define WOLFSSL_AES_128 +#define WOLFSSL_AES_256 +#define WOLFSSL_AES_DIRECT +#define WOLFSSL_AES_CFB +#define HAVE_AES_DECRYPT +#define HAVE_AES_CBC + +#define WOLFSSL_SHA224 +#define WOLFSSL_SHA384 +#define WOLFSSL_SHA512 +#define WOLFSSL_SHA3 +#define WOLFSSL_SHAKE128 +#define WOLFSSL_SHAKE256 +#define HAVE_HKDF +#define WOLFSSL_NO_SHAKE128_ASM + +/* Post-quantum: ML-DSA (FIPS 204) and ML-KEM (FIPS 203), TPM 2.0 v1.85. */ +#define WOLFSSL_HAVE_MLDSA +#define WOLFSSL_HAVE_MLKEM +#define WOLFSSL_MLDSA_NO_ASN1 +#define WOLFSSL_EXPERIMENTAL_SETTINGS + +/* OP-TEE's compiler.h defines a `fallthrough` macro. wolfCrypt's types.h + * 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. Settle it here instead. */ +#define FALL_THROUGH ; __attribute__((__fallthrough__)) + +/* Keep large temporaries off the small TA stack. */ +#define WOLFSSL_SMALL_STACK + +/* Not reachable from the TPM command set. */ +#define NO_DH +#define NO_DSA +#define NO_DES3 +#define NO_RC4 +#define NO_MD4 +#define NO_MD5 +#define NO_PSK +#define NO_OLD_TLS +#define NO_PKCS12 +#define NO_PWDBASED +#define WOLFSSL_NO_PKCS8 + +/* No allocator or RNG shims 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. + * + * The native dry run is a host build, so it gets neither. NO_FILESYSTEM above + * is right for the trusted application but also closes the host seed path, so + * the harness stands in for the TEE RNG the same way it stands in for NV. */ +#ifdef FWTPM_TA_NATIVE + #ifndef WOLFSSL_NO_PORT_DECLS + int fwtpm_native_rand(unsigned char* out, unsigned int sz); + #endif + #define CUSTOM_RAND_GENERATE_BLOCK fwtpm_native_rand + #define WC_NO_HASHDRBG +#endif + +#endif /* FWTPM_TA_USER_SETTINGS_H */ diff --git a/README.md b/README.md index 1d86c96..562eaa8 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,19 @@ Firmware TPM 2.0 implementation for NUCLEO-H563ZI (Cortex-M33). Supports TrustZo Added in https://github.com/wolfSSL/wolftpm-examples/pull/1 +## NVIDIA Jetson Orin fwTPM in OP-TEE + +See [NVIDIA/fwtpm-jetson-optee](NVIDIA/fwtpm-jetson-optee). + +Firmware TPM 2.0 running as an OP-TEE trusted application in the secure world +on a Jetson Orin, with unmodified wolfTPM examples driving it from Linux over +libteec. NV in OP-TEE secure storage, journal integrity key derived from the +hardware unique key. + +The Jetson already ships a firmware TPM, but it reports TPM 2.0 spec revision +1.62 and so has no post-quantum support. This port puts a revision 1.85 TPM on +the same silicon alongside it, with ML-DSA and ML-KEM. + ## Xilinx ZCU102 fwTPM on Cortex-R5 Lock-Step See [Xilinx/fwtpm-zcu102-r5](Xilinx/fwtpm-zcu102-r5).