Measured Boot Implementation with OP-TEE and TF-A on Jetson Orin Nano

Hello everyone,

I have set up JetPack SDK on my Jetson Orin Nano, flashed it with a Custom Kernel and set up OP-TEE along with Disk Encryption.

Now, I am trying to implement Measured Boot, with the goal of using the measured data for attestation in the future. While researching, I came across a presentation titled “Measured Boot Support in Trusted Firmware-A (TF-A) project”, created by Arm Ltd. employees. This presentation shows that TF-A already has a Measured Boot implementation using a specific driver.

My main question is: Is it possible to use this TF-A driver even though I am working with OP-TEE? I am somewhat unsure about this because, in many places, the TF-A Developer Guide and NVIDIA’s documentation seem to be closely intertwined. Additionally, while following the setup instructions, I ended up with an arm-trusted-firmware directory inside /Linux_for_Tegra/source, which was explicitly required at some point in the process. This strongly suggests that OP-TEE and TF-A are designed to work together. However, at the same time, they are technically different TEE OS implementations, which makes me wonder whether this TF-A Measured Boot driver can be used in my setup.

If you need more details (e.g., about my setup), please let me know.

Additionally, if using this driver is possible, could someone guide me on how to build it correctly?
I have already tried the following command while being in “/home/nik71841/jetpack/jp62/target/Linux_for_Tegra/source/atf_build/arm-trusted-firmware”:

make PLAT=tegra TARGET_SOC=t234 MEASURED_BOOT=1 TRUSTED_BOARD_BOOT=1 TPM_HASH_ALG=sha256

With that command I was able to build a bl31 folder in “/home/nik71841/jetpack/jp62/target/Linux_for_Tegra/source/atf_build/arm-trusted-firmware/build/tegra/t234/release” along with the folders “lib, libc, libwrapper and romlib”. But I have no idea if thats correct or what to do with that to enable / test measured boot.

hello nik01flink,

please refer to the digram, The fTPM Boot Flow.

In our secure boot flow, ATF and OP-TEE are combined as a single image (TOS). In the secure boot flow, the tos image would be verified and measured by MB2 for Measured boot support. i.e. the TOS image (TF-A + OP-TEE) is measured boot supported.

Herry JerryChang,

okay, so from what you’ve told me, and from what I’ve read in the TF-A Developer Guide, it should be quite possible to use this Measured Boot driver. I mean .. I even managed to build it.

Do you have any idea what I need to do with the newly built bl31 folder? Do I simply have to reflash the jetson or create a new image? Sadly there is no image in the bl31 folder itself that I could somehow replace with the existing one.

hello nik01flink,

you have to take that bl31.bin to re-create TOS image. it’s A_secure-os partition to locate TOS image.
please download public release sources, you may extract atf_src.tbz2 and nvidia-jetson-optee-source.tbz2, and please see-also atf_and_optee_README.txt for reference.

Hey,

many thanks for the tip. I have now replaced the completely newly created release folder (where bl31.bin and the rest is in) with the existing release folder in /home/nik71841/jetpack/jp62/target/Linux_for_Tegra/source/atf_build/arm-trusted-firmware/generic-t234/tegra/t234. And as described in the instructions atf_and_optee_README.txt, the new tos-optee image is built with it.
I then reflashed the jetson, do you have any tips on how to check whether measured boot is active?

hello nik01flink,

since the Measured Boot support of fTPM is based on PCR (Platform Configuration Register).
please check PCR to confirm whether measured boot is active.
for instance,
$ sudo modprobe tpm_ftpm_tee
$ sudo tpm2_pcrread

Hey JerryChang,

I have to take another step back. I wrote in my last message that my jetson was flashed. That’s true, the flash ends with ‘flash successfull’ just as normal, but I didn’t connect a monitor back then to check whether the jetson was booting correctly. Unfortunately, I don’t get a picture when I start the jetson with the new image. The fan spins, but there is no display port signal. I can’t find the error, so here are the steps I took in detail:

Delete old tegra-directory (inside generic-t234-directory):

cd /home/nik71841/jetpack/jp62/target/Linux_for_Tegra/source/atf_build/arm-trusted-firmware/generic-t234
rm -r tegra

Build bl31-directory (inside build-directory) and move it to generic-t234-directory:

cd /home/nik71841/jetpack/jp62/target/Linux_for_Tegra/source/atf_build/arm-trusted-firmware
make PLAT=tegra TARGET_SOC=t234 MEASURED_BOOT=1 TRUSTED_BOARD_BOOT=1 TPM_HASH_ALG=sha256
cp -r /home/nik71841/jetpack/jp62/target/Linux_for_Tegra/source/atf_build/arm-trusted-firmware/build/tegra /home/nik71841/jetpack/jp62/target/Linux_for_Tegra/source/atf_build/arm-trusted-firmware/generic-t234

Delete old tos-optee_t234.img:

cd /home/nik71841/jetpack/jp62/target/Linux_for_Tegra/bootloader
rm tos-optee_t234.img

Build tos.img, move it to bootloader-directory and rename it to tos-optee_t234.img:

cd /home/nik71841/jetpack/jp62/target/Linux_for_Tegra/nv_tegra/tos-scripts/
./gen_tos_part_img.py \
    --monitor /home/nik71841/jetpack/jp62/target/Linux_for_Tegra/source/atf_build/arm-trusted-firmware/generic-t234/tegra/t234/release/bl31.bin \
    --os /home/nik71841/jetpack/jp62/target/Linux_for_Tegra/source/jetson-optee-srcs/nvidia-jetson-optee-source/optee/build/t234/core/tee-raw.bin \
    --dtb /home/nik71841/jetpack/jp62/target/Linux_for_Tegra/source/jetson-optee-srcs/nvidia-jetson-optee-source/optee/tegra234-optee.dtb \
    --tostype optee \
    ./tos.img
mv tos.img /home/nik71841/jetpack/jp62/target/Linux_for_Tegra/bootloader/tos-optee_t234.img

Flash jetson:

cd /home/nik71841/jetpack
export JP=jp62
make all

This ended with ..

Flash is successful
Reboot device
Cleaning up...
Log is saved to Linux_for_Tegra/initrdlog/flash_1-2_0_20250326-170932.log 
say done

For your information this is my Makefile:

JP ?= <invalid path>
MODEL ?= 234
KERNEL_VERSION ?= 5.15
KERNEL_BUILD ?= $(KERNEL_VERSION).148-tegra

DEV ?= /dev/sda
F_IMG ?= <select img>

NPROCS := 1
JETPACK_PTH := $(PWD)/$(JP)
TOOLCHAIN := $(JETPACK_PTH)/toolchain
KERNEL_TARGET := $(JETPACK_PTH)/target/Linux_for_Tegra
PUBLIC_SRC := $(KERNEL_TARGET)/source
OPTEE_SRC := $(PUBLIC_SRC)/jetson-optee-srcs/nvidia-jetson-optee-source
KERNEL_SRC := $(PUBLIC_SRC)/kernel
KERNEL_HEADERS := $(PUBLIC_SRC)/kernel/kernel-jammy-src

CROSS_COMPILE := $(TOOLCHAIN)/bin/aarch64-buildroot-linux-gnu-

CARGS := CROSS_COMPILE=$(CROSS_COMPILE) \
	 KERNEL_HEADERS=$(KERNEL_HEADERS) \
	 INSTALL_MOD_PATH=$(KERNEL_TARGET)/rootfs 

kernel:
	cd $(PUBLIC_SRC) && \
		$(CARGS) make -C kernel && \
		$(CARGS) sudo -E make install -C kernel

config:
	./scripts/set-config.sh -i $(JP)/configs/defconfig -k $(KERNEL_HEADERS)


modules:
	cd $(PUBLIC_SRC) && \
		$(CARGS) make modules && \
		$(CARGS) sudo -E make modules_install && \
	cd $(KERNEL_TARGET) && \
		sudo ./tools/l4t_update_initrd.sh

dtbs:
	cd $(PUBLIC_SRC) && \
		$(CARGS) make dtbs && \
		cp kernel-devicetree/generic-dts/dtbs/* $(KERNEL_TARGET)/kernel/dtb

flash:
	cd $(KERNEL_TARGET) && \
	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 -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
			
all: config kernel modules dtbs flash
	say done

A quick update:

I had previously renamed the newly generated tos.img to tos-optee_t234.img using:

mv tos.img /home/nik71841/jetpack/jp62/target/Linux_for_Tegra/bootloader/tos-optee_t234.img

This time, I also tried renaming the same tos.img to tos_t234.img instead — just to test if the name tos-optee_t234.img was causing the issue. Unfortunately, that didn’t help either.

To understand what’s going wrong, I compared logs from different flash attempts (all of them flashed successfully):

  1. Old tos_t234.img & old tos-optee_t234.img
  2. Old tos_t234.img & new tos.img renamed to tos-optee_t234.img
  3. Old tos_optee_t234.img & new tos.img renamed to tos_t234.img

Interestingly, between flashes 1 and 3, the logs are nearly identical (except for some keys), but only flash 1 shows a display signal — flash 3 does not.

Between flash 1 and flash 2 (which should be the correct method according to the official instructions — using tos-optee_t234.img), there is a notable difference.

Specifically, in step 2 (“Boot the device with flash initrd image”), flash 1 logs:

Added binary blob_tos_t234_sigheader.img.encrypt of size 1641424

But flash 2 logs:

Added binary blob_tos-optee_t234_sigheader.img.encrypt of size 1637248

So, the old working flash uses blob_tos_t234_sigheader.img.encrypt, and the new one uses blob_tos-optee_t234_sigheader.img.encrypt. That’s a significant difference.

However, when I tested the new image under the name tos_t234.img, which then led to blob_tos_t234_sigheader.img.encrypt being used again — it still didn’t work. But I guess that is just due to the wrong approach (the image should not be named tos_t234.img).

And sorry for all the messages — but just to follow up, I tested the commands you suggested using flash 1 (i.e., the old images without MEASURED_BOOT=1), even though of course the intention is to get things working with the new image.

Here’s the output:

jetson@jetson-desktop:~$ sudo modprobe tpm_ftpm_tee
[sudo] password for jetson: 
jetson@jetson-desktop:~$ sudo tpm2_pcrread
  sha1:
    0 : 0x0000000000000000000000000000000000000000
    1 : 0x0000000000000000000000000000000000000000
    2 : 0x0000000000000000000000000000000000000000
    3 : 0x0000000000000000000000000000000000000000
    4 : 0x0000000000000000000000000000000000000000
    5 : 0x0000000000000000000000000000000000000000
    6 : 0x0000000000000000000000000000000000000000
    7 : 0x0000000000000000000000000000000000000000
    8 : 0x0000000000000000000000000000000000000000
    9 : 0x0000000000000000000000000000000000000000
    10: 0x0000000000000000000000000000000000000000
    11: 0x0000000000000000000000000000000000000000
    12: 0x0000000000000000000000000000000000000000
    13: 0x0000000000000000000000000000000000000000
    14: 0x0000000000000000000000000000000000000000
    15: 0x0000000000000000000000000000000000000000
    16: 0x0000000000000000000000000000000000000000
    17: 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
    18: 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
    19: 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
    20: 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
    21: 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
    22: 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
    23: 0x0000000000000000000000000000000000000000
  sha256:
    0 : 0x0000000000000000000000000000000000000000000000000000000000000000
    1 : 0x0000000000000000000000000000000000000000000000000000000000000000
    2 : 0x0000000000000000000000000000000000000000000000000000000000000000
    3 : 0x0000000000000000000000000000000000000000000000000000000000000000
    4 : 0x0000000000000000000000000000000000000000000000000000000000000000
    5 : 0x0000000000000000000000000000000000000000000000000000000000000000
    6 : 0x0000000000000000000000000000000000000000000000000000000000000000
    7 : 0x0000000000000000000000000000000000000000000000000000000000000000
    8 : 0x0000000000000000000000000000000000000000000000000000000000000000
    9 : 0x0000000000000000000000000000000000000000000000000000000000000000
    10: 0x0000000000000000000000000000000000000000000000000000000000000000
    11: 0x0000000000000000000000000000000000000000000000000000000000000000
    12: 0x0000000000000000000000000000000000000000000000000000000000000000
    13: 0x0000000000000000000000000000000000000000000000000000000000000000
    14: 0x0000000000000000000000000000000000000000000000000000000000000000
    15: 0x0000000000000000000000000000000000000000000000000000000000000000
    16: 0x0000000000000000000000000000000000000000000000000000000000000000
    17: 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
    18: 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
    19: 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
    20: 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
    21: 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
    22: 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
    23: 0x0000000000000000000000000000000000000000000000000000000000000000

hello nik01flink,

do you have serial console logs? we may dig into booting message for details.
or,
is boot stuck due to waiting for system configuration? you may try Skipping oem-config to create a default user, and flashing again for verification. for instance, $ l4t_create_default_user.sh -u user -p passwd -a

Thanks for your reply! I will test it later.

But what came into my mind .. since there is not a single boot log when starting the jetson after the flash, is it possible that measured boot is blocking the boot process now? (Even before actions like comparing keys for disk encryption can take place)

Hey JerryChang,

a lot has come up, so I’ve only just got round to replying.
Actually, skipping the oem config helped, thanks for the tip. I could now successfully boot the jetson with the tos-optee_t234.img I built with measured boot.

Only the commands you gave me to test measured boot still output exactly the same as before (see my last message from 28th march). The tpm registers do not change.

With measured boot enabled, shouldn’t there be a file somewhere that stores the boot process data?

please see-also Topic 328636, you’ll need to update the firmware components (such as mb1, mb2, TOS..etc) for changing hash values.

Many thanks for the tip. However, I’m wondering whether this will really help me. After all, I only get the initial values displayed in the registers after booting or flashing. If Measured Boot were to work, the event log would have to be copied to the registers, or am I misunderstanding?

While researching, I came across the fact that in the OPTEE source code that was required and generated at the time using the atf_and_optee_README.txt instructions for setting up OP-TEE, the script optee_src_build.sh was used. In this script there is the option to set Measured Boot = 1.
Do I perhaps have to rebuild the source code with measured boot in addition to the newly generated bl31 folder (where I set measured boot = 1 in the build process), which was used for the tos image?

hello nik01flink,

you may check the bootloader logs, for example, here’re the logs by default (without Jetson security)

I> Task: Measured Boot init
I> Task: fTPM silicon identity init
I> fTPM is not enabled.
...

I/TC: Test OEM keys are being used. This is insecure for shipping products!
I/TC: fTPM ID is not enabled.
I/TC: ftpm-helper PTA: fTPM DT or EKB is not available. fTPM provisioning is not supported.

Hey JerryChang,

First of all, I want to clarify that I am using a discrete TPM module, not an fTPM. I think that might not have been clear previously. As you suggested, I had a look at the boot log. Regarding measured boot it only contained the default lines:

I> Task: Measured Boot init
I> Task: fTPM silicon identity init
I> fTPM is not enabled.
...

This made me suspect that measured boot might not be functioning properly, because if it were, I would expect to see more than the default logs.

According to NVIDIA’s documentation on fTPM Measured Boot, the PCRs should receive their values from the event log. So I tried looking into the directory /sys/kernel/security/ima/ascii_runtime_measurements, but found it didn’t exist. A quick investigation revealed that the Integrity Measurement Architecture (IMA) is normally responsible for creating this event log. Therefore, I modified the configuration file at:

/home/nik71841/jetpack/jp62/target/Linux_for_Tegra/source/out/nvidia-linux-header/arch/arm64/configs/deconfig

I added the following lines to enable IMA:

...
CONFIG_INTEGRITY=y
CONFIG_IMA=y
CONFIG_IMA_MEASURE_PCR_IDX=10
CONFIG_IMA_APPRAISE=y
CONFIG_IMA_APPRAISE_BOOTPARAM=y
CONFIG_IMA_DEFAULT_HASH="sha256"
CONFIG_INTEGRITY_SIGNATURE=y

I also rebuilt the OP-TEE sources, because I found that the script optee_src_build.sh includes logic for enabling measured boot only if ENABLE_FTPM_BUILD is set to yes:

if [ "${TARGET_PLATFORM}" == "t234" ] && [ "${ENABLE_FTPM_BUILD}" == "yes" ]; then
    ftpm_config="CFG_TA_MEASURED_BOOT=y \
                 CFG_USE_PLATFORM_EPS=y"
    ...
fi

However, when I initially built the sources, I didn’t use the -t flag (which sets ENABLE_FTPM_BUILD=yes). So the code block that includes CFG_TA_MEASURED_BOOT=y was never executed.

After correcting this, I rebuilt everything and reflashed the Jetson. Unfortunately, when I run tpm2_pcrread, the PCR values are still empty after boot. However, I did notice something different in the boot log (Minicom somehow didn’t get the formatting right, sorry for that):

jetson@tegra-ubuntu:~$ lws ss /sys/kernel/security/ima/ascii_runtime_measurements
ls: cannot access '/sys/kernel/security/ima/ascii_runtime_measurements': No such file or directory
jetson@tegra-ubuntu:~$ ls /sys/kernel/security/ima/ascii_runtime_measurements
ls: cannot access '/sys/kernel/security/ima/': No such file or directory
jetson@tegra-ubuntu:~$ ls /sys/kernel/security/ima/aaaaaaaaa
integrity  lsm

So I’m guessing measured boot is unable to copy the hash from the event log into the PCR because the event log doesn’t exist — is that possible?
Do I need to manually create the directory structure for the IMA event log, or is there an additional step required to properly enable it?

hello nik01flink,

please note that, we do not support DTPM for Jetson, yet.
you’ll need to build/load your HW TPM driver, and run the TPM2 commands with root privilege. if the driver is ready, you should be able to see the device node, such as.. /dev/tpm0 and /dev/tpmrm0.

just an FYI,
the fTPM feature is default enabled in JP-6 public release version.
if you rebuild op-tee without -t options, the fTPM feature will not be included, it’ll lead to rpc related errors in UEFI.

Hey,

Yes, I’m aware that discrete TPMs are not officially supported yet, but since my TPM driver is working (I can see the device nodes you mentioned when running ls /dev | grep tpm), I thought it should be possible to get it working with ARM’s measured boot implementation.

I also looked into the optee_src_build.sh script and saw that measured boot is only activated when the -t flag is passed. I did make sure to include that flag during my last build to enable CFG_TA_MEASURED_BOOT=y. Unfortunately, that didn’t help either. My boot log currently looks like this:

savelog.txt (78.1 KB)

Just to summarize what I’ve done so far to try and enable measured boot — since things have gotten a bit messy:

  1. Built the bl31 binary using:

make PLAT=tegra TARGET_SOC=t234 MEASURED_BOOT=1 TRUSTED_BOARD_BOOT=1 TPM_HASH_ALG=sha256

  1. Generated a new tos-optee_t234.img using the gen_tos_part_img.py script (with the updated bl31) and placed it in the bootloader directory
  2. Added the following to the flash process to work around a black screen issue after flashing:
l4t_create_default_user.sh -u jetson -p test
  1. Enabled the IMA subsystem in the Linux kernel configuration
  2. Used

./optee_src_build.sh -p t234 -t

to rebuild the OP-TEE source tree, specifically the build and install directories inside
/home/nik71841/jetpack/jp62/target/Linux_for_Tegra/source/jetson-optee-srcs/nvidia-jetson-optee-source/optee, ensuring that the measured boot TA was built with CFG_TA_MEASURED_BOOT=y and included in the final image
6) Reflashed the Jetson with the updated images and kernel (successfully flashed)

Despite all of this, my PCRs are not extended, I don’t see any useful messages in the boot log, and the IMA event log (ascii_runtime_measurements) is completely missing. Do you have any ideas on what I might be missing here?

hello nik01flink,

for the case of dTPM to support measured boot during early boot and UEFI stage, you need the driver to support in the FW layer as well.

for example,

  • OP-TEE OS: The dTPM driver and minimal function to support tpm2_pcrextend command are needed to support the PCR extend from the TPM event log buffer.
  • UEFI: Please check if the UEFI already has dTPM driver you are using. If yes, you can try to re-use it. With the dTPM driver support, UEFI should be able to support measure boot.
    • In case if UEFI measure boot is supported, you can use the 2 commands below to check;
    • The PCR contents should be the same (except PCR 0 depends on the measured boot support in early boot stage).
      • tpm2_eventlog /sys/kernel/security/tpm0/binary_bios_measurements
      • tpm2_pcrread

Hi JerryChang,

I think the topic has already been closed because I haven’t posted for a while, but unfortunately I haven’t been able to solve the problem yet. I’d like to give you a quick update on my progress.

First, I realized that using the -t flag with optee_src_build.sh (which sets CFG_TA_MEASURED_BOOT=y) doesn’t apply to my case. I’m working with a discrete TPM (Infineon SLB9670 over SPI), not the fTPM provided by OP-TEE. So enabling this flag had no effect, and I’ve removed it from my workflow.

Initially, I also assumed that IMA was required for measured boot — I believed the event log needed to come from IMA. Because of that, I enabled various CONFIG_IMA_* options in the kernel (e.g., CONFIG_IMA, CONFIG_IMA_MEASURE_PCR_IDX, CONFIG_IMA_APPRAISE, etc.). I now understand that this was incorrect, since IMA is only relevant in the Linux userspace and not during early boot, where measured boot actually takes place. I’ve since reverted those kernel config changes.

The most promising development came when I discovered that Arm recently pushed a dedicated TPM driver to their TF-A repository. This driver (which includes SPI FIFO support for SLB9670) defines the SPI pins. I’ve integrated this driver into my TF-A build by adding it to the platform_t234.mk, included the necessary headers, and compiled it successfully using nvbuild.sh.

However, the core issue remains: I don’t see any PCR extensions during boot, nor do I see any TPM-related messages in the boot log. There’s no event log, and attempts to call tpm_pcr_extend() inside bl31_platform_setup() don’t seem to have any visible effect. The TPM driver is clearly compiled and linked, but it seems that it’s either not properly initialized or not being invoked at the right stage.

At this point, I suspect I might still be missing an essential setup step in TF-A — perhaps proper GPIO init, or the driver may require coordination with the UEFI firmware stage, as you previously mentioned.

Do you have any guidance on how to correctly integrate and invoke the TPM driver during early boot? For example, where exactly should tpm_interface_init() or tpm_pcr_extend() be placed to have measurable effect? I’d be happy to share my current TF-A patch if that helps.

Thanks again and best regards

1 Like