Trusted Platform Module (TPM in NVIDIA Jetson platforms)

From RidgeRun Developer Wiki


NVIDIA partner logo NXP partner logo






This wiki shows how to implement the fTPM function for NVIDIA Jetson Platforms. This tutorial was tested with a Jetson Orin Nano module running JetPack 6; remember that some steps may vary slightly for other versions of JetPack. It is assumed that JetPack sources were already installed and contained in a directory used to flash the target board initially. It is important to know that the fTPM implementation is based in OP-TEE, so first check out RidgeRun's OPTEE tutorial for Jetson platforms to learn more about it. Additionally, fuse burning is part of this tutorial which requires access to an FSKP package from NVIDIA and is a non reversible operation. Due to this, check RidgeRun's Secure Boot tutorial for Jetson platforms to learn more about fuse burning and FSKP.

Introduction

Jetson platforms support a TPM 2.0 compliant Firmware TPM (fTPM) implementation. This implementation works by using a secure application running with OP-TEE. The TA and CA pair make the implementation work, the TA serves as the fTPM module while the CA allows to retrieve data from the fTPM. The following diagram shows the how fTPM implementation:

Fig 1. fTPM architecture in Jetson Platforms. Extracted from link

Implementation

As a first step, follow RidgeRun's OPTEE tutorial for Jetson platforms to ensure the target board has support for OPTEE.

Key generation

During the implementation process several cryptographic keys will be needed, and these keys will be divided in two groups. The first group is used for Secure Boot while the second group is used for the Encrypted Key Blob (EKB) generation. Set the working directory to the path where the Linux for Tegra directory is contained, this tutorial will refer to this directory as BSP_TOP and it should have a structure similar to the following:

BSP_TOP
├── Linux_for_Tegra

Go to the directory and set its path as an environment variable:

export BSP_TOP=$(pwd)

Then generate the first set of keys:

cd $BSP_TOP
mkdir -p odm_keys && cd odm_keys

openssl genrsa -out rsa3k.pem 3072

openssl rand -rand /dev/urandom -hex 32 > sbk-32.key

Generate the second set of keys:

cd $BSP_TOP
mkdir -p oem_keys && cd oem_keys

openssl rand -rand /dev/urandom -hex 32 > oem_k1.key

openssl rand -rand /dev/urandom -hex 32 > sym_t234.key

openssl rand -rand /dev/urandom -hex 16 > sym2_t234.key

openssl rand -rand /dev/urandom -hex 16 > auth_t234.key

Rebuild OPTEE sources

By this point it is assumed you already followed the steps in RidgeRun's OPTEE tutorial for Jetson platforms. OPTEE sources must be rebuild to enable the fTPM feature, to do so, add the -t flag when building the sources:

cd $BSP_TOP/Linux_for_Tegra/source/tegra/optee-src/nv-optee/

./optee_src_build.sh -p t234 -t

After the process is finished, update the TOS image that will be used for your build:

cp tos.img $BSP_TOP/Linux_for_Tegra/bootloader/tos-optee_t234.img

EKB Generation

The first step is to generate the EKB database using KDK-based EK certificates. It is possible to do this using an automated script provided by NVIDIA with the following commands:

cd $BSP_TOP/Linux_for_Tegra/source/optee/samples/ftpm-helper/host/tool

./kdk_gen.py [--oem_id ${OEM_ID}] [--sn ${SN}] [--num_devices ${NUM_OF_DEVICES}]

# Example:
./kdk_gen.py --oem_id 0x21 --sn 0x1000218000 --num_devices 1

With the previous example the database will be generated for only one device using the provided OEM ID and serial number. The script will create a directory called ftpm_kdk containing the files kdk_db_0021-0000001000218000-1.csv and pubkey_db_0021-0000001000218000-1.csv. The content of this files for the example look like the following:

cat kdk_db_0021-0000001000218000-1.csv 
0021 0000001000218000 83899b63f8a81b26e7785ed6be3fbaf54d05bf88078efb7cce9b2e7e2b904ed8
# Where the first two columns are oem-id and SN, and the last column is the randomly generated KDK value.

cat pubkey_db_0021-0000001000218000-1.csv 
0021 0000001000218000 b789dfc2efce60fd538444c8f80218d8fe696493aec58234d7f4167da047a31c94f0a3f6163c74535f5342881bba15cf5624eb31d0ce5b7c0564dbf1e84c3a67
# Where the first two columns are oem-id and SN, and the last column is the corresponding silicon-id public key.

With the KDK database ready, use the following command to generate the ODM EKB:

./odm_ekb_gen.py --kdk_db ftpm_kdk/kdk_db_0021-0000001000218000-1.csv

The previous command generates the EK certificates in the ftpm_out directory and the ODM EKB in the odm_out directory. Now generate the signed EKB using the previously generated keys:

cp ../../../hwkey-agent/host/tool/gen_ekb/gen_ekb.py ./
./oem_ekb_gen.py -oem_k1_key $BSP_TOP/oem_keys/oem_k1.key \
                  -in_sym_key $BSP_TOP/oem_keys/sym_t234.key \
                  -in_sym_key2 $BSP_TOP/oem_keys/sym2_t234.key \
                  -in_auth_key $BSP_TOP/oem_keys/auth_t234.key \
                  -in_ftpm_odm_ekb odm_out

# The outputs from the above command are stored at the oem_out Directory. Copy this directory to the BSP_TOP directory.
cp -r oem_out/ $BSP_TOP

Then, to sign the generated image and EKB to comply with the secure boot fuses that will be burned execute the following commands:

cd $BSP_TOP/Linux_for_Tegra
./l4t_sign_image.sh --chip 0x23 \
                     --key ../odm_keys/rsa3k.pem \
                     --encrypt_key ../odm_keys/sbk-32.key \
                     --mass-ekb ../oem_out \
                     --type data \
                     --split False

Fuseblob Generation

This section shows how to generate the required Fuseblob for the fTPM implementation and how to executed the fuse burning process using the FSKP tool. To learn more about fuse burning check RidgeRun's Secure Boot tutorial for Jetson platforms.

Remember that in order to burn the fuses you require an FSKP package from NVIDIA. When you have the package ready, the next step is to create a fuse template xml file. This file is used to indicate which fuses are going to be burned and the value to be burned into them. In the case of fTPM, create the file to look like the following:

# Example fuse.xml file:
<genericfuse MagicId="0x45535546" version="1.0.0">
<fuse name="OdmInfo" size="4" value="0x21"/>
<fuse name="OdmId" size="8" value="0x1000218000"/>
<fuse name="Kdk0" size="32" value="0x83899b63f8a81b26e7785ed6be3fbaf54d05bf88078efb7cce9b2e7e2b904ed8"/>
<fuse name="OemK1" size="32" value="0x14fe024d38615b543ea1ce3895463ec8ebb2b29b2bee93b71813eda8319c495d"/>
<fuse name="PublicKeyHash" size="64" value="0xb789dfc2efce60fd538444c8f80218d8fe696493aec58234d7f4167da047a31c94f0a3f6163c74535f5342881bba15cf5624eb31d0ce5b7c0564dbf1e84c3a67"/>
<fuse name="SecureBootKey" size="32" value="0xe450f7f38e256c1cfcb28e1ac6b50aebc89bdfb00c6a3e81ea2701fd80402b81"/>
<fuse name="BootSecurityInfo" size="4" value="0x9"/>
</genericfuse>

In the previous example, OdmInfo and OdmId are the ID and serial number used when generating the KDK data based, Kdk0 is KDK value generated for the device, PublicKeyHash is the public key generated in the KDK process and OemK1 and SecureBootKey are the corresponding keys by the same name that were previously generated and used. The value for the BootSecurityInfo fuse must be set according to the specification of the Jetson Orin Fuse Specification Application Note, in this case, a value of 0x9 will enable secure boot using a 3072-bit RSA key.

Before generating the fuseblob specification information must be retrieved from the target board. To do so, execute the following command:

cd ${BSP_TOP}/Linux_for_Tegra
sudo ./flash.sh --read-info jetson-orin-nano-devkit internal

# Example output:
Board ID(3767) version(300) sku(0005) revision(K.2)
Preset RAMCODE is 2
Chip SKU(00:00:00:D5) ramcode(2) fuselevel(fuselevel_production) board_FAB(300)
ECID is 0x80012344705DE5196C000000100102C0

With the fuse.xml file and the information ready, the next step is to generate the fuseblob that will be used to burn the fuses. To do so, use the following commands:

# Note: before doing this you should have the FSKP package ready and uncompressed following the instructions in the secure boot guide
cd ${BSP_TOP}/Linux_for_Tegra/l4t/tools/flashtools/fuseburn/

The information in the orinnano-board-spec.txt file must be modified to match the information retrieved from the target board. After updating the file, disconnect the board from the host machine and execute the following command to generate the fuseblob:

sudo ./fskp_fuseburn.py -f ../fskp/fuse.xml \
                        --multi-blob ${BSP_TOP}/Linux_for_Tegra/source/optee/samples/ftpm-helper/host/tool/ftpm_kdk/kdk_db_0021-0000001000218000-1.csv
                        -b \
                        -i 63 \
                        --key-exp ../fskp/fskp_ak.bin ../fskp/fskp_ek.bin \
                        --fskpcfg ../fskp/fskp_conf.txt \
                        -g fuseblob/odm_out \
                        -c 0x23 \
                        --board-spec orinnano-board-spec.txt \
                        -B ../../../../jetson-orin-nano-devkit.conf

When the process is done the fusblob will be located at the fuseblob/odm_out directory. The next step is to burn the fuses, remember this is a non reversible operation, so make sure that the correct fuse.xml file was used and that it contains the correct values to be burned in the fuses. If a mistake was made, delete the previous fuseblob and follow the process again to generate a new fuseblob with the correct values to burn. After making sure the desired values were used, put the target board in recovery mode, connect it to the host machine and execute the following command to burn the fuses:

sudo ./fskp_fuseburn.py -c 0x23 \
                        -P fuseblob/odm_out/ \
                        --board-spec orinnano-board-spec.txt \
                        -B ../../../../jetson-orin-nano-devkit.conf \
                        -b

Flash Image Generation

In this section the images for flashing the board will be generated, specifically the QSPI and UPI images. First, to generate the QSPI images execute the following commands:

cd ${BSP_TOP}/Linux_for_Tegra
sudo rm -rf tools/kernel_flash/images/

# Note: the following board information should match the information previously retrieved from the board.

sudo BOARDID=3767 FAB=300 BOARDSKU=0005 CHIP_SKU=00:00:00:D6 RAMCODE_ID=2 \
./tools/kernel_flash/l4t_initrd_flash.sh \
                        --odm-image-gen \
                        --showlogs \
                        --network usb0 \
                        --no-flash \
                        -u ../odm_keys/rsa3k.pem \
                        -v ../odm_keys/sbk-32.key \
                        -p "-c bootloader/generic/cfg/flash_t234_qspi.xml" \
                        jetson-orin-nano-devkit \
                        internal

And then generate the UPI image:

sudo BOARDID=3767 FAB=300 BOARDSKU=0005 CHIP_SKU=00:00:00:D6 RAMCODE_ID=2 \
./tools/kernel_flash/l4t_initrd_flash.sh \
                        --mass-storage-only \
                        --showlogs \
                        --network usb0 \
                        --no-flash \
                        jetson-orin-nano-devkit \
                        internal

The output of the following commands are lbc_odm.tar.gz and upi_oem.tar.gz respectively. Due to this, the previous packages must be decompressed before flashing board, which can be done with the following commands:

sudo tar zvxf lbc_odm.tar.gz

sudo tar zvxf upi_oem.tar.gz

After that, copy the signed EKB to the images folder:

sudo mkdir -p tools/kernel_flash/images/internal/ekb_db
sudo cp ${BSP_TOP}/oem_out/signed/* tools/kernel_flash/images/internal/ekb_db

Finally, put the board in recovery mode, connect it to the host machine and perform the flashing process:

sudo ./tools/kernel_flash/l4t_initrd_flash.sh --external-device mmcblk0p1 \
			-c tools/kernel_flash/flash_l4t_external.xml \
			-p "-c bootloader/generic/cfg/flash_t234_qspi.xml" \
			--showlogs \
			--network usb0 \
			jetson-orin-nano-devkit \
			internal