Accessing fTPM-helper from a Trusted Application on Jetson

Hello everyone,

my goal is (ideally) to access the TPM from inside a Trusted Application.

Background / what I tried

My first attempt was to use a discrete TPM, but this ended up being a dead end due to missing drivers on Jetson. That’s why I would now like to work with the fTPM (which is available in the OP-TEE samples).

As I understand it, the Microsoft fTPM only works when certain fuses are burned. In one of my earlier forum posts, I was told that there is an “fTPM simulation feature after r36.4 release version”. I followed the related documentation (which also mentions Keylime), and was able to use swtpm successfully. With tpm2_pcrread I could see PCR registers changing while the simulation was running.

However, this is of course just the software TPM (swtpm), not the Microsoft fTPM TA. As far as I understand, the MS fTPM is a separate TA inside OP-TEE, which should be accessed via the fTPM helper / fTPM driver (CA ↔ TA ↔ fTPM TA).

Current state

  • I built and installed nvftpm-helper-app.

  • The corresponding TA file for the fTPM helper is placed in the TA directory.

  • Running sudo nvftpm-helper-app --help works fine.

  • But running sudo nvftpm-helper-app -g (query ECID) or -i (query provisioning mode) fails with:

    ca_query_prov_mode: TEEC_InvokeCommand failed 0xffff0008 origin 0x4
    Invalid provision mode!
    

When I check the supplicant:

$ sudo systemctl restart tee-supplicant
Failed to restart tee-supplicant.service: Unit tee-supplicant.service not found.

$ sudo tee-supplicant
ERR [5749] TEES:main:942: failed to find an OP-TEE supplicant device

I also don’t see any /dev/tee* device nodes.

And importantly: if I try to run nvftpm-helper-app inside the swtpm simulation environment from the Keylime guide, it also fails with the same error.

My questions

  1. Is it required to have /dev/tee* device nodes available in order to use the Microsoft fTPM TA?
  2. From what I understand, this looks like a provisioning problem. In the earlier simulation setup (with swtpm) this was solved by starting a process in user space. But since in this case I want to work with multiple TAs inside OP-TEE, I assume I cannot just “start” the actual Microsoft fTPM TA in the same way, correct? Is this really something fundamentally different, or could swtpm already serve my use case?

hello nik01flink,

could you please test below and sharing the results.
$ sudo modprobe tpm_ftpm_tee
$ sudo nvftpm-helper-app -j

Hello,

I tried the suggested test:

$ sudo modprobe tpm_ftpm_tee
$ sudo nvftpm-helper-app -j
ca_query_prov_mode: TEEC_InvokeCommand failed 0xffff0008 origin 0x4
Invalid provision mode!

So unfortunately, the result is still the same as before.

For context: I initially followed the SWTPM simulation setup guide (as described in the Keylime documentation). That guide requires starting SWTPM in user space as root:

sudo apt-get install tpm2-tools swtpm-tools

sudo mkdir /tmp/swtpm
sudo chown tss:root /tmp/swtpm
sudo swtpm_setup --tpmstate /tmp/swtpm --create-ek-cert --create-platform-cert --tpm2

sudo su
swtpm socket --tpmstate dir=/tmp/swtpm --tpm2–server type=tcp,port=2321–ctrl type=tcp,port=2322–flags not-need-init,startup-clear

sudo su
export TPM2TOOLS_TCTI="swtpm:port=2321"
mkdir swtpm_cert
cp /var/lib/swtpm-localca/swtpm-localca-rootca-cert.pem swtpm_cert/
cp /var/lib/swtpm-localca/issuercert.pem swtpm_cert/
cd swtpm_cert
tpm2_nvread 0x01c00002 -o swtpm_rsa_ek_cert.der
openssl x509 -inform DER -outform PEM -in swtpm_rsa_ek_cert.der -out swtpm_rsa_ek_cert.pem
openssl verify -CAfile swtpm-localca-rootca-cert.pem --untrusted issuercert.pem -show_chain swtpm_rsa_ek_cert.pem

Does this mean that the nvftpm-helper-app cannot be used with the SWTPM simulation at all, and only works once the actual Microsoft fTPM TA is provisioned and available inside OP-TEE?

Thanks,
Niklas

hello nik01flink,

the only interface to communicate with the TPM is the TPM2 command interface.

Yes, the fTPM TA is based on OP-TEE.

that’s correct. Please refer to below for the evaluation steps on the un-fused board.

To obtain a valid provisioning mode, you must complete the provisioning process.
Please refer to the “Firmware TPM” section of the developer guide.

For early evaluation, please try the script ftpm_sim_provisioning_tool.sh.
This script can help you provision the fTPM on an unfused board for testing purposes.
You can find the script at the source code location below.
$public_sources/r36.4.4/Linux_for_Tegra/source/atf_and_optee/optee/samples/ftpm-helper/host/tool/

Please copy the script “ftpm_sim_provisioning_tool.sh” and “conf/” folder onto the device.
And using the command below to provision and clear the device:

# Provisioning the fTPM (for test only)
./ftpm_sim_provisioning_tool.sh ek_prov

# Clear fTPM content
./ftpm_sim_provisioning_tool.sh clear

Hello,

okay, as I suspected, it looks like I will eventually have to go through the fuse burning step in order to use the actual Microsoft fTPM. Since the provisioning tool required for fuse burning is only provided on request, I already contacted NVIDIA Sales via this form about a month ago. Unfortunately, I haven’t received any response since then.

Would it be possible for you to help me with this, or point me to the right contact so that I can obtain the required tool?

Thank you in advance!

Best regards,
Niklas

hello nik01flink,

may I double check what’s the tool you’re asking?
if you’re going to fuse a platform, you may using odmfuse.sh script to burn fuse locally.

Hey,

sorry for not replying for a while. After your hint I have carefully read the Secure Boot documentation in the Developer Guide. I saw that odmfuse.sh is deprecated, but it should still work according to the docs. So I did the preparation and tried it … Unfortunately, the problem now is that my Jetson Orin Nano does not start anymore. When I connect the power supply after the flash, the fan does not spin and I get no log at all via UART. The flash itself finished correctly though.

Here are exactly the steps I followed:

  1. Generated RSA-3K key pair:
openssl genrsa -out rsa_priv.pem 3072
openssl rsa -in rsa_priv.pem -pubout -out rsa_pub.pem
  1. Created PublicKeyHash with tegrasign:
python3 tegrasign_v3.py --pubkeyhash rsa_pub.pem rsa_pub.hash --key rsa_priv.pem
  1. Inserted the hash into fuse_config.xml:
<genericfuse MagicId="0x45535546" version="1.0.0">
    <fuse name="PublicKeyHash" size="64" value="<MY_HASH>"/>
    <fuse name="BootSecurityInfo" size="4" value="0x1"/>
    <fuse name="SecurityMode" size="4" value="0x1"/>
</genericfuse>
  1. Test run with odmfuse:
sudo ./odmfuse.sh -i 0x23 -X fuse_config.xml jetson-orin-nano-devkit --test

This ended with:

[ 5.0459 ] Sending bct_mem
[ 5.0460 ] Sending blob
[ 6.4660 ] completed
[ 6.4661 ] Finish
  1. Actual fuse burn:
sudo ./odmfuse.sh -i 0x23 -X fuse_config.xml jetson-orin-nano-devkit
  1. Adjusted my flash command like this (only added -u rsa_priv.pem – the other steps already worked before):
sudo ./tools/l4t_create_default_user.sh -u jetson -p test -a --accept-license && \
sudo ./tools/kernel_flash/l4t_initrd_flash.sh --network usb0 \
    --showlogs -p "-c bootloader/generic/cfg/flash_t234_qspi.xml" \
    --no-flash jetson-orin-nano-devkit internal && \
sudo ROOTFS_ENC=1 ./tools/kernel_flash/l4t_initrd_flash.sh --network usb0 \
    --showlogs --no-flash --external-device mmcblk0p1 -u rsa_priv.pem -S 16GiB \
    -c ./tools/kernel_flash/flash_l4t_t234_nvme_rootfs_enc.xml \
    --external-only --append -i ./sym2_t234.key jetson-orin-nano-devkit external && \
sudo ./tools/kernel_flash/l4t_initrd_flash.sh --network usb0 --showlogs --flash-only

The flash finished without any visible error. Here is the log:

flash_1-2_0_20251002-125839.log (68.6 KB)

But now the board does not react at all when I connect power(no fan, no UART log). But I see a green light and I can still set it into recovery mode.

Did I miss an important step?

Any advice would be very helpful.

Thanks in advance,
Niklas

hello nik01flink,

could you please setup serial console to obtain the bootloader logs, we’ll need to check the booting messages for details.

Hey,

I’ve tried to read the logs using both minicom and screen. As usual (before fusing this setup always worked), I connected the UART first and then powered on the Jetson. However, nothing appears on the console — there’s simply no output at all. The Jetson’s fan also doesn’t spin up.

hello nik01flink,

let’s try reading out the fuse variable if you may still put it into recovery mode.
for instance, $ sudo ./flash.sh --read-info -u <pkc> -v <sbk> <target_conf> <rootdev>

if that’s stuck in recovery mode..
did you have jumper to connect pin-9 and pin-10 from J14 to put system into forced-recovery mode? you may remove that for confirmation.

Hello,

I tried to read out the fuse information as you suggested.
I ran the following command inside the Linux_for_Tegra directory:

sudo ./flash.sh --read-info -u /home/nik71841/fuseBurning/rsa_priv.pem jetson-orin-nano-devkit internal

The device could still be put into recovery mode and was detected via lsusb.
The command executed successfully and generated the fuse_t234.bin file among other files in the bootloader directory with the following context:

PublicKeyHash: ca37d293213eac8c77d7c91e81e7fa07a3ea533629b16ee3a8d6eecb694eae5404a8922cac4082ead56958b49518f2978d33d52a87a220f1c1d42250d2de8e98
PkcPubkeyHash1: 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
PkcPubkeyHash2: 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
BootSecurityInfo: 00000001
ArmJtagDisable: 00000000
SecurityMode: 00000001
SwReserved: 00000000
DebugAuthentication: 00000000
OdmInfo: 00000000
OdmId: 0000000000000000
OdmLock: 00000000
ReservedOdm0: 00000000
ReservedOdm1: 00000000
ReservedOdm2: 00000000
ReservedOdm3: 00000000
ReservedOdm4: 00000000
ReservedOdm5: 00000000
ReservedOdm6: 00000000
ReservedOdm7: 00000000
Sku: 000000d5
Uid: c08102100000003c17e55d7004000000
OptEmcDisable: 0000000c

So the connection in recovery mode seems to work fine.

Best,
Niklas

hello nik01flink,

here’re several questions..
Q1. according to Jetson Orin Fuse Specification of FUSE_BOOT_SECURITY_INFO_0, you’ve only fused PKC key with 3072-bit RSA, right?
Q2. let me double check which platform you’re working with, is it Orin Nano developer kit?
Q3. let me double check the Jetpack public release version you’re using, you may also try download a fresh new Jetpack image for verification.

besides..
let’s try below flash command-line to test your fused target again.
(1) image creation.
$ sudo ADDITIONAL_DTB_OVERLAY_OPT="BootOrderNvme.dtbo" ./tools/kernel_flash/l4t_initrd_flash.sh --no-flash --external-device nvme0n1p1 -c tools/kernel_flash/flash_l4t_external.xml -p "-c bootloader/generic/cfg/flash_t234_qspi.xml" -u rsa_priv.pem --showlogs --network usb0 jetson-orin-nano-devkit internal
(2) image flashing
$ sudo ./tools/kernel_flash/l4t_initrd_flash.sh --flash-only -u rsa_priv.pem jetson-agx-orin-devkit internal

Thanks for your reply and for clarifying the steps.

Q1: Yes, that’s correct — I only fused the PKC key using a 3072-bit RSA key. I followed this example.
Q2: The platform I’m using is the Jetson Orin Nano Developer Kit.
Q3: I’m currently using JetPack 6.2 (L4T R36.4.3).

Following your suggestion, I modified the flash section in my Makefile.
Originally, I used the following flash block:

sudo ./tools/kernel_flash/l4t_initrd_flash.sh --network usb0 \
	--showlogs -p "-c bootloader/generic/cfg/flash_t234_qspi.xml" \
	--no-flash jetson-orin-nano-devkit internal && \
	sudo ROOTFS_ENC=1 ./tools/kernel_flash/l4t_initrd_flash.sh --network usb0 \
		--showlogs --no-flash --external-device mmcblk0p1 -u /home/nik71841/fuseBurning/rsa_priv.pem -S 16GiB \
		-c ./tools/kernel_flash/flash_l4t_t234_nvme_rootfs_enc.xml \
		--external-only --append -i ./sym2_t234.key jetson-orin-nano-devkit external && \
	sudo ./tools/kernel_flash/l4t_initrd_flash.sh --network usb0 --showlogs --flash-only

I changed it according to your recommendation to the following:

sudo ADDITIONAL_DTB_OVERLAY_OPT="BootOrderNvme.dtbo" \
	./tools/kernel_flash/l4t_initrd_flash.sh \
	--no-flash \
	--external-device mmcblk0p1 \
	-c tools/kernel_flash/flash_l4t_external.xml \
	-p "-c bootloader/generic/cfg/flash_t234_qspi.xml" \
	-u /home/nik71841/fuseBurning/rsa_priv.pem \
	--showlogs \
	--network usb0 \
	jetson-orin-nano-devkit internal && \
	sudo ./tools/kernel_flash/l4t_initrd_flash.sh \
		--flash-only \
		-u /home/nik71841/fuseBurning/rsa_priv.pem \
		jetson-agx-orin-devkit internal

The flashing process completed successfully this time, and the board actually started showing boot logs again via UART.
However, the boot process now stops with the following output:

ÿäI/TC: Reserved shared memory is disabled
I/TC: Dynamic shared memory is enabled
I/TC: Normal World virtualization support is disabled
I/TC: Asynchronous notifications are disabled
E/TC:?? 00 jetson_user_key_pta_uefi_vars_auth:984 UEFI variable auth key not set !
E/TC:?? 00 stmm_handle_variable_authentication:894 Failed to get signed CMAC ffff0008

ASSERT [FvbNorFlashStandaloneMm] /out/nvidia/optee.t234-uefi/StandaloneMmOptee_RELEASE/edk2-nvidia/Silicon/NVIDIA/Drivers/FvbNorFlashDxe/VarIntCheck.c(932): ((BOOLEAN)(0==1))

The fuse burn seems to be correct but it looks like it’s failing during the authentication step, so I assume the issue is related to a missing or invalid UEFI variable authentication key (EKB/EKS)?
I thought the EKB is only needed when the OemK1 is burned.

Best,
Niklas

hello nik01flink,

according to the failure,

such assert at UEFI due to no OEM_K1 key, it’s due to a module has OEM_K1 key fused, which cannot boot with default trusty image.
please running gen_ekb.py to re-create EKS image, and flashing to the target for confirmation.

Hi Jerry,

although the original topic is already closed, I need to follow up because I’m still seeing the same issue even after running odmfuse.sh.

Current state:

  • The device is fused

  • I regenerated the EKB/EKS using gen_ekb.py with oem_k1.key, sym_t234.key, sym2_t234.key, and auth_t234.key

  • I updated flashing: I now flash both QSPI (secure boot / UEFI) and the external rootfs on mmcblk0p1 with disk encryption enabled, using the same sym2_t234.key in the process. The board now boots Linux with Secure Boot active and an encrypted rootfs, so that part looks correct

    Script:

    flashing_script.txt (785 Bytes)

On the running system:

  • nv-tee-supplicant is active, /dev/tee0 and /dev/teepriv0 are present.

  • The kernel module tpm_ftpm_tee is loaded.

  • The fTPM helper TA is installed, and I built/installed nvftpm-helper-app.

When I run:

sudo nvftpm-helper-app -j

I get:

TEEC_InvokeCommand failed 0xffff0008 origin 0x4
Invalid provision mode!

The TA seems reachable in OP-TEE, but functionally I’m still blocked: nvftpm-helper-app reports Invalid provision mode!, /dev/tpm0 is still not reliably exposed, and I cannot use tpm2_getcap / tpm2_pcrread against the firmware TPM.

Do you see what step I’m still missing to get a fully provisioned fTPM (/dev/tpm0, usable PCRs)?

Best,
Niklas

Log after flash:

bootlog.txt (74.4 KB)

Log after running nvftpm helper app:

uart_log_helper_app.txt (323 Bytes)

hello nik01flink,

please refer to post #8, did you try running the script ftpm_sim_provisioning_tool.sh?

Hi Jerry,

you were right — I had overlooked the provisioning step. I copied the conf folder and ftpm_sim_provisioning_tool.sh to the Jetson, made it executable, and tried to run it. I’m seeing the same issue I had before:

jetson@tegra-ubuntu:~/ftpm_sim$ sudo ./ftpm_sim_provisioning_tool.sh ek_prov
[fTPM SIM Provisioning][FOR DEV ONLY]: Taking TPM ownership.
ERROR:tcti:src/tss2-tcti/tcti-device.c:452:Tss2_Tcti_Device_Init() Failed to open specified TCTI device file /dev/tpmrm0: No such file or directory
ERROR:tcti:src/tss2-tcti/tctildr-dl.c:154:tcti_from_file() Could not initialize TCTI file: device
ERROR:tcti:src/tss2-tcti/tctildr.c:428:Tss2_TctiLdr_Initialize_Ex() Failed to instantiate TCTI
ERROR: Could not load tcti, got: "device:/dev/tpmrm0"

(Without sudo there’s no output at all.) So I assume this swTPM path only runs in user space (as README_keylime_TPM_for_unfused_Jetson.md also states).

I then checked ftpm_device_provision.sh, which seems to be the provisioning tool needed to get nvftpm-helper working — is that correct? The script expects EK certificate inputs (e.g., ek_cert_rsa.der and ek_cert_ec.der), and since I only fused PKC in my fuse config, I don’t have those certificates.

Here is my fuse readout:

BootSecurityInfo: 00000001
SecurityMode:     00000001
OdmInfo:          00000000
OdmId:            0000000000000000
Kdk0:             (not present in dump)

Given this, it looks like I can’t use the firmware TPM anymore, except via the user-space simulated TPM (e.g., with Keylime). Or am I misunderstanding something? My immediate goal is simply to use the nvftpm-helper.