RidgeRun Platform Security Manual/Getting Started/Secure Boot

From RidgeRun Developer Wiki




NVIDIA partner logo NXP partner logo






Secure Boot

Secure Boot on an NVIDIA Jetson system is activated to block the execution of code coming from untrusted sources. To achieve it, NVIDIA Jetson SoCs use public key cryptography and the Root of Trust. The private key is used to sign the codes that are going to be executed when the board is initially booting and the public key is going to be stored in devices called fuses inside the board. To begin the booting process on an NVIDIA Jetson Xavier/Orin board, first a BootROM code is executed, which is the first element of the Root of Trust for these systems. It loads and autheticates the first codes that are going to be executed in the booting process. It authenticates by generating a public key hash from the private key digital signature embedded in each code, and compares it to the public key hash stored in the devices called fuses. If the keys match, the code comes from a trusted source and can be executed, otherwise it is not and the booting process is halted. So then, to activate a secure boot on an NVIDIA Jetson SoC, it is necessary to generate a PKC (Public Key Cryptography) key pair, store the public key hash on the fuses and sign the boot codes with the corresponding private key. As a relevant note, a fuse device can only be written once to, so it is important to do it right. Check out the [Secure boot] general page in this wiki for more information. In this guide we are going to take a look at how to activate this feature on a Jetson Orin Nano. Below is a general diagram of the process:

  • This guide was tested using a Jetson Orin Nano Developer Kit, but with slight modifications, can be applied to Jetson Orin NX series, Jetson AGX Orin series, the Jetson Xavier NX series, and the Jetson AGX Xavier series. Specifically, the commands for generating the fuse blob are platform dependent, as well as flash commands.
  • This guide was tested using Ubuntu 20.04 LTS on the host(laptop where JetPack is installed used to flash the NVIDIA Jetson Orin/Xavier board).

Factory Secure Key and Expansion Key Provisioning

Factory Secure Key and Expansion Key Provisioning (FSKP) is a technique to improve the security when burning fuses on an SoC. For reference, "burning" the fuses refers to storing a public key hash in them, which can only be done once. Before, a script named odmfuse was used to do this process, and it was purely managed by the OEM (Original Equipment Manufacturer), a.k.a. the person or team building the final product. Now, the additional security measure that FSKP brings, is that it encrypts the data passed to the SoC for fuse burning. This adds a layer of protection to the process. The fuse data is encrypted and sent to the SoC and later decrypted inside it. It will be encrypted with expansion keys provided by an NVIDIA representative, and decrypted with a key already on the IROM (internal Read Only Memory) on the SoC.

Fig 1. FSKP process diagram

To begin the process, first generate an RSA key pair and a certificate from it. This is done to send the certificate to the NVIDIA representative and get the expansion keys encrypted.

Generate 4096 bits RSA public and private keys:

* aes-256-cbc: 256 bit keys to encrypt RSA key with AES (Advanced Encryption Standard).
* out oem_rsa_priv.pem 4096: Specifies the output file name and the size of the RSA key.

This command is going to ask for a passphrase to encrypt. Keep it in mind for the next steps. The output looks like the following:

<syntaxhighlight lang="bash">
Generating RSA private key, 4096 bit long modulus (2 primes)
....................++++
........++++
e is 65537 (0x010001)
Enter pass phrase for oem_rsa_priv.pem:
Verifying - Enter pass phrase for oem_rsa_priv.pem:

This passphrase is like a password for the key. This helps to protect the private key if someone gets access to the file that holds it. It encrypts it so that the only way to read it is by knowing this passphrase.

Generate RSA x509 based Certification:

openssl req -new -x509 –outform PEM -key oem_rsa_priv.pem -out oem_publickey.cer -days 365
  • new: Indicates that a new certificate should be generated.
  • x509: Specifies that a self-signed certificate should be generated, rather than a CSR (Certificate Sign Request). A self-signed certificate is one that is signed by its own private key.
  • outform PEM: Specifies the output format of the certificate as PEM (Privacy Enhanced Mail), which is a common format for storing certificates.
  • key oem_rsa_priv.pem: Specifies the private key file to use for signing the certificate.
  • out oem_publickey.cer: Specifies the output file name for the generated certificate. The certificate will be saved to a file named oem_publickey.cer. Although the extension is ".cer" which is common, the -outform PEM option means the file will contain PEM encoded data.
  • days 365: Specifies the validity period of the certificate in days. This value should be set accordingly to the security policies of the OEM.

The output is going to ask you for the passphrase to read the private key and generate the certificate. It is going to also ask for information about your organization to add on the certificate:

Enter pass phrase for oem_rsa_priv.pem:
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:CR
State or Province Name (full name) [Some-State]:San Jose
Locality Name (eg, city) []:San Jose
Organization Name (eg, company) [Internet Widgits Pty Ltd]:RidgeRun
Organizational Unit Name (eg, section) []:RidgeRun
Common Name (e.g. server FQDN or YOUR name) []:RidgeRun
Email Address []:ridgerun_inquiries@ridgerun.com

RSA 4096 bits is used by NVIDIA Recommendation. Email this CER file to the NVIDIA key custodian to get the expansion keys back and some other files to complete the process. The files needed are going to come in a zip file called Results.zip. There are two expansion keys, the AK expansion key (FSKP_AK) and the EK expansion key (FSKP_EK). Below are the contents of the Results.zip file with a basic explanation for each.

  • fskp_ak.bin.rsa_wrap: The FSKP_AK wrapped with the RSA public key (binary). This is the file to be decrypted to obtain the AK key as binary data.
  • fskp_ak.bin.rsa_wrap.info: metadata, Label, and String/Context used in KDF (Key Derivation Function), and KCV (Key Check Value) of FSKP_AK.
  • fskp_ek.bin.rsa_wrap: FSKP_EK wrapped with RSA public key (binary). This is the file to be decrypted to obtain the EK key as binary data.
  • fskp_ek.bin.rsa_wrap.info: metadata, Label, and String/Context used in KDF (Key Derivation Function), and the KCV (Key Check Value) of FSKP_EK.
  • fskp_conf.txt: includes the AK and EK strings to encrypt the fuse data.

As reference for some concepts previously mentioned:

  • KDF or Key Derivation Function: A function to generate the expansion keys from a root key. In this case, the root key is the Expansion key. This is the key that the NVIDIA representative has access to and is reference in the SoC with key index 62, and is stored in the IROM.
  • KCV or Key Check Value: A Key Check Value is a short cryptographic checksum of a cryptographic key. It's used to verify the integrity of a key, ensuring that it hasn't been altered or corrupted.

You should also receive an FSKP tool package containing the instruments needed for the fuse burning process.

Prepare the necessary directories

To begin the encryption of the fuse data, we need to set up the necessary directories. First install the NVIDIA's board support package by downloading the Driver Package (BSP) and Sample Root Filesystem from the drivers section on the following link. Extract the downloaded files with the following commands inside a work directory (nvidia jetson in our case):

mkdir nvidia-jetson

tar xf Jetson_Linux_R36.4.0_aarch64.tbz2 -C nvidia-jetson/
sudo tar xpf Tegra_Linux_Sample-Root-Filesystem_R36.4.0_aarch64.tbz2 -C \
nvidia-jetson/Linux_for_Tegra/rootfs

Run the following scripts inside the Linux for tegra directory:

cd nvidia-jetson/Linux_for_Tegra

sudo ./tools/l4t_flash_prerequisites.sh

sudo ./apply_binaries.sh

Output should look like the following:

.
.
.
L4T BSP package installation completed!
Disabling NetworkManager-wait-online.service
Disable the ondemand service by changing the runlevels to 'K'
Success!

(Optional) Create a Default User

 sudo ./tools/l4t_create_default_user.sh -u <user_name> -p <password>

With NVIDIA's board support package installed, untar the FSKP tool package inside the Linux for Tegra directory. Assuming the FSKP tool package tar file was downloaded to the nvidia-jetson directory, use the following command:

tar xvjf ../fskp_partner_t234_R3x.x.0_aarch64.tbz2

The resulting directory is named l4t. Unzip the Results.zip file inside l4t/tools/flashtools/fuseburn/Results directory. Assuming you downloaded the Results.zip file inside the nvidia-jetson directory:

cp ../Results.zip l4t/tools/flashtools/fuseburn/Results

cd l4t/tools/flashtools/fuseburn/Results

unzip Results.zip

Decrypt AK and EK expansion keys binary data:

openssl rsautl -decrypt -inkey oem_rsa_priv.pem -in fskp_ak.bin.rsa_wrap > fskp_ak.bin

openssl rsautl -decrypt -inkey oem_rsa_priv.pem -in fskp_ek.bin.rsa_wrap > fskp_ek.bin

Move these files from the Results directory to the fuseburn directory (one level above):

mv fskp_ak.bin ..
mv fskp_ek.bin ..
mv fskp_conf.txt ..
Prepare and encrypt the fuse data

The encrypted fuse binary data is called a fuse blob. This file is going to be decrypted in the SoC with the IROM stored key. Before going over how to prepare it, make sure the board specifications and configuration files have the right information about the board. The main variables to take into account are:

  • BOARDID
  • CHIP_SKU
  • BOARDSKU
  • RAMCODE_ID
  • FAB

You can get the information of these variables by running the following command. This is for an NVIDIA Jetson Orin Nano with an SD Card. Remember to keep the board connected in recovery mode and with a USB-C to USB cable:

sudo ./flash.sh --read-info jetson-orin-nano-devkit internal

Almost at the end of the output, there should be a similar text to the following with the values we are looking for.

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

Take the values obtained from the previous output and write on the board specification file inside the l4t/ tools/flashtools/fuseburn. In our case it is named orinnano-board-spec.txt. With the output obtained for the board we are using, the file modified should look like below:

Linux_for_Tegra/l4t/tools/flashtools/fuseburn$ cat orinnano-board-spec.txt 
BOARDID=3767
BOARDSKU=0004
FAB=000
CHIP_SKU=00:00:00:D6
FUSELEVEL=fuselevel_production
RAMCODE_ID=4

The most common values for Orin boards are in the following table:

Board Orin AGX Orin NX Orin Nano
BOARDID 3701 3767 3767
CHIP_SKU 00:00:00:D0 00:00:00:D3 00:00:00:D6
BOARD_SKU 0004 0000 0004
RAMCODE_ID 0 0 4
FAB 000 000 000

There should also be information about the current state of the fuses, similar to the following:

PublicKeyHash: 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
PkcPubkeyHash1: 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
PkcPubkeyHash2: 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
BootSecurityInfo: 00000000
ArmJtagDisable: 00000000
SecurityMode: 00000000
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: c00201100000006c19e55d7004000000
OptEmcDisable: 0000000c

Which as we can see has zeros in all of them because we have not write to them. Once a 1 has been written to one of the bits of the fuses, it cannot be reversed. The next step to activate secure boot is to generate the keys that are going to be stored in the fuses. For this example we are going to just use the PublicKeyHash fuse and SecureBootKey fuse. The Public key is use for boot code authentication and the secure boot key is used for boot codes encryption and decryption.

Starting with the Public key used for authenticating the boot codes, it can be generated as an RSA 3K key, ECDSA P-256 or ECDSA P-521 key. For reference, these are two types of algorithms for genetring a PKC pair and the numbers are just the size of the keys. In terms of security RSA 3K and ECDSA P-256 provide a similar level of security but ECDSA is more efficient because it needs less storage. ECDSA P-521 provides a higher security level the the previous algorithms explained and has a lower size than RSA 3K. RSA 3K is a more known/applied/compatible algorithm. For this guide, an RSA 3K key is going to be used. To generated, create a keys folder inside the Linux for Tegra directory:

mkdir keys
cd keys
openssl genrsa -out rsa_priv.pem 3072

the output should look like the following:

Generating RSA private key, 3072 bit long modulus (2 primes)
.................................................................++++
...................................................................++++
e is 65537 (0x010001)

A file named rsa_priv.pem should be in the directory. To generate the Public key hash, the tegrasign_v3.py script can be used to generate it, use this command from the keys directory:

./tegrasign_v3.py --pubkeyhash rsa_priv.pubkey rsa_priv.hash --key rsa_priv.pem

the output looks like the following:

Key size is 384 bytes
Saving pkc public key in rsa_priv.pubkey
Sha saved in pcp.sha
tegra-fuse format (big-endian): 0x513a1d8764dbd5d700932d18db4f48da4c13094b4986660c4525d69ef2307ceaba65ddec675bacc8bbeb2c91d31f1e7fb98be4472a354010ff7dea49510b7b48

make sure to save the result because it is going to be need on the fuse configuration file explained below.

Now, the secure boot key is randomly generated with a size of 32 bits. Specifically, the Orin SoC requires an SBK key of eight 32-bit words. You can use the following commands to generate it:

SBK_0=$(openssl rand -hex 4)
SBK_1=$(openssl rand -hex 4)
SBK_2=$(openssl rand -hex 4)
SBK_3=$(openssl rand -hex 4)
SBK_4=$(openssl rand -hex 4)
SBK_5=$(openssl rand -hex 4)
SBK_6=$(openssl rand -hex 4)
SBK_7=$(openssl rand -hex 4)
SBK_KEY=$(echo "0x${SBK_0} 0x${SBK_1} 0x${SBK_2} 0x${SBK_3} 0x${SBK_4} 0x${SBK_5} 0x${SBK_6}
0x${SBK_7}")
echo "${SBK_KEY}" > sbk.key
SBK_KEY_XML=$(echo "0x${SBK_0}${SBK_1}${SBK_2}${SBK_3}${SBK_4}${SBK_5}${SBK_6}${SBK_7}")
echo "${SBK_KEY_XML}" > sbk_xml.key

A sbk.key and a sbk_xml.key file are now in the directory. the second one has the value that is going to be used in the fuse configuration file.

In this case we use openssl to generate the 8 random 32 byte words. It is used because it's a widely adopted, open-source cryptographic toolkit that provides robust functionality for securing communications and data. But you can use the tool of your choice.

The next step is to create the fuse configuration file. The fuse configuration file is a xml file that states that which fuse is burned with x value. It has three main tags which are name, size and value. In the same order, the tags are for the fuse name, its size in bytes and the value to be burned on to the fuse. There is a list of fuses on the Orin SoCs, but we are going to use only the PublicKeyHash, SecureBootKey and BootSecurityInfo fuses. For reference, below is the Jetson Orin reference configuration file for your reference:

<genericfuse MagicId="0x45535546" version="1.0.0">
    <!-- <fuse name="OdmId" size="8" value="0xFFFFFFFFFFFFFFFF"/> -->
    <!-- <fuse name="OdmInfo" size="4" value="0xFFFF"/> -->
    <!-- <fuse name="ArmJtagDisable" size="4" value="0x1"/> -->
    <!-- <fuse name="DebugAuthentication" size="4" value="0x1F"/> -->
    <!-- <fuse name="CcplexDfdAccessDisable" size="4" value="0x1"/> -->
    <!-- <fuse name="ReservedOdm0" size="4" value="0xFFFFFFFF"/> -->
    <!-- <fuse name="ReservedOdm1" size="4" value="0xFFFFFFFF"/> -->
    <!-- <fuse name="ReservedOdm2" size="4" value="0xFFFFFFFF"/> -->
    <!-- <fuse name="ReservedOdm3" size="4" value="0xFFFFFFFF"/> -->
    <!-- <fuse name="OdmLock" size="4" value="0xF"/> -->
    <!-- <fuse name="ReservedOdm4" size="4" value="0xFFFFFFFF"/> -->
    <!-- <fuse name="ReservedOdm5" size="4" value="0xFFFFFFFF"/> -->
    <!-- <fuse name="ReservedOdm6" size="4" value="0xFFFFFFFF"/> -->
    <!-- <fuse name="ReservedOdm7" size="4" value="0xFFFFFFFF"/> -->
    <!-- <fuse name="OptInEnable" size="4" value="0x1"/> -->
    <!-- <fuse name="SwReserved" size="4" value="0xFFFFFF"/> -->
    <!-- <fuse name="BootDevInfo" size="4" value="0xFFFFFF"/> -->
    <!-- <fuse name="ZeroizeDis" size="4" value="0x1"/> -->
    <!-- <fuse name="PublicKeyHash" size="64" value="0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"/> -->
    <!-- <fuse name="PkcPubkeyHash1" size="64" value="0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"/> -->
    <!-- <fuse name="PkcPubkeyHash2" size="64" value="0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"/> -->
    <!-- <fuse name="EndorseKey" size="68" value="0x1FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"/> -->
    <!-- <fuse name="SecureBootKey" size="32" value="0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"/> -->
    <!-- <fuse name="Kdk0" size="32" value="0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"/> -->
    <!-- <fuse name="PscOdmStatic" size="4" value="0xFFFFFFFF"/> -->
    <!-- <fuse name="OemK1" size="32" value="0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"/> -->
    <!-- <fuse name="OemK2" size="32" value="0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"/> -->
    <!-- <fuse name="BootSecurityInfo" size="4" value="0xFFFFFFFF"/> -->
    <!-- <fuse name="SecurityMode" size="4" value="0x1"/> -->
</genericfuse>

It is also important to check the Jetson Orin Fuse Specification: Application Note to get information of the fuses being burned.

Create and open a fuse_config.xml file with your favourite text editor and add the fuse burning data. In our case it looks like the following. As a note MagicId of “0x45535546” is used by the target-binary and must not be changed.:

<genericfuse MagicId="0x45535546" version="1.0.0">
    <fuse name="PublicKeyHash" size="64" value="0x99a6b7d25ffd5d7cc49bf2612d01d7fe58b5121f9c473748728232bc114c25ae2415d56666157c79fc9bf0e3b4445344ff8af51a64f334289912cdff7414fa00"/>
    <fuse name="SecureBootKey" size="32" value="0xf4d8f0c3180f8b2b430d89e1eb0d600c01f99be7a3e9045d4b27d621de571d57"/>
    <fuse name="BootSecurityInfo" size="4" value="0x9"/>
</genericfuse>

With the fuse data generated, we can proceed with the encryption of it. In this process we will also activate the decryption of the fuse blob (fuse data encrypted) and the fuse burning in the board. To generate the fuse blob use the following command:

sudo ./fskp_fuseburn.py -b -f ~/nvidia-jetson/Linux_for_Tegra/keys/fuse_config.xml -i 63 --key-exp fskp_ak.bin fskp_ek.bin --fskpcfg fskp_conf.txt -g out/ -c 0x23 -B ~/work/devdir/security-features-RnD/nvidia-jetson4/Linux_for_Tegra/jetson-orin-nano-devkit.conf --board-spec orinnano-board-spec.txt -v
  • -b: Performs fuse burning. In this command, this option is used to generate a real fuse blob but the next command is the one used for actually burning the fuses.
  • -f ~/nvidia-jetson/Linux_for_Tegra/keys/fuse_config.xml: specifies the fuse configuration file to generate the fuse blob.
  • -i 63: Specifies the key index. This is used to select the key from the internal ROM to decrypt the information. The expansion keys are generated from this key by the NVIDIA representative, and they are the "public key " in this case and the key from the internal ROM is the private one. Key index 63 is used in this case because this is a key for debugging purposes but behaves equally to the one used for production purposes, which is key 62.
  • --key-exp fskp_ak.bin fskp_ek.bin: Specifies the expansion keys.
  • --fskpcfg fskp_conf.txt: Specifies the fskp configuration file that has the expansion keys string. This file is included with the expansion keys sent by the NVIDIA representative.
  • -g out/: Specifies the output directory for the fuse blob.
  • -c 0x23: Specifies the chip id, which for Jetson Orin SoCs is 0x23.
  • -B ~/nvidia-jetson/Linux_for_Tegra/jetson-orin-nano-devkit.conf: Specifies the board configuration file. It is already in the Linux_for_Tegra folder. Choose according to the board you are using.
  • --board-spec orinnano-board-spec.txt: Specifies the board specification file previously edited.

The output should look similar to the following:

FSKP execution started 2025-03-14 12:59:55.523192
fskp_fuseburn.py script version 0.2
Parsing input arguments
fskp_fuseburn.py script version 0.2
Parsing input arguments
Setting up default paths
Setup host environment
fskp enviroment internal: False
Creating t234 fuse blob
.
.
.
Not burning fuses, exiting...
FSKP execution successful
FSKP execution time 0:00:09.341467

You should get the successful message at the end of the command's output. To actually burn the fuses run the following command.

sudo ./fskp_fuseburn.py --board-spec orinnano-board-spec.txt -P ./out -b -c 0x23 -B ~/work/devdir/security-features-RnD/nvidia-jetson4/Linux_for_Tegra/jetson-orin-nano-devkit.conf

The option added in this command is the -P option. It has the argument out/ and it is used to specify that it is no required to generate a new folder with the fuse blob but to use the out directory, that already has it. You should get an output like the following:

FSKP execution started 2025-03-14 13:43:33.137784
fskp_fuseburn.py script version 0.2
Parsing input arguments
fskp_fuseburn.py script version 0.2
Parsing input arguments
Setting up default paths
Setup host environment
Found NVIDIA device ID 0x7523
Getting target details
BR_CID: 0x80012344705DE5196C000000100102C0
ECID: 0x4705DE5196C000000100102C0
WARNING!! Burning Fuses option is selected, this operation is permanent and irreversible
if you are not sure, try with --test or -t command line option
do you want to continue burning fuses (Yes/No) yes
Downloading FSKP blob to target

WARNING!! Target will automatically reset once burning fuses is complete.
          If you are going to continue doing secure NOR provisioning, please DO NOT power off the system
FSKP execution successful
FSKP execution time 0:00:07.774871

It asks if you are sure about the operation because it is irreversible, write yes if you are. If you have the UART output from the board You should look for the following messages:

I> Task: Burn fuses                                                                        
I> Index : 1    PublicKeyHash    size: 64                                                  
I> Index : 2    SecureBootKey    size: 32                                                  
I> Index : 3    BootSecurityInfo    size: 4                                                
I> Fuse Blob found                                                                         
I>                                                                                         
I> Burning fuses                                                                           
I> 1. Start PublicKeyHash burn                                                             
I> 1. PublicKeyHash burnt successfully                                                     
W> No handling of CRC-32 for PublicKeyHash                                                 
I>                                                                                         
I> 2. Start SecureBootKey burn                                                             
I> 2. SecureBootKey burnt successfully                                                     
W> No handling of CRC-32 for SecureBootKey                                                 
I>                                                                                         
I> 3. Start BootSecurityInfo burn                                                          
I> 3. BootSecurityInfo burnt successfully                                                  
W> No handling of CRC-32 for BootSecurityInfo                                              
I>                                                                                         
I> Successfully burnt fuses as per fuse info

Secure boot is succesfully enabled. From now on, all the images and boot codes you load to the board have to be signed with the private keys created in this process.

UEFI Secure Boot

UEFI secure boot is a process to prevent the execution of the codes it loads, that are not from a trusted source, a more in depth explanation is found in the UEFI Secure Boot section in the NVIDIA Jetson example case from the general [Secure Boot] page on this wiki. It is a compliment to the secure boot previously explained since it authenticates the codes that follow initial boot process prior to the execution of the UEFI bootloader.

How does UEFI Secure Boot Work?

UEFI Secure Boot employs RSA digital signatures to authenticate and verify the integrity of the code it loads during startup. Below are the main components used:

  • Platform Key (PK) : Top-level key, is used to sign KEK.
  • Key Exchange Key (KEK) : Keys used to sign Signatures Database.
  • Signature Database (db) : Contains keys to sign UEFI payloads.

These keys and the keys database are first saved as a UEFI authenticated variable. This is done so that when UEFI payloads are loaded, they are verified looking for the associated certificate/key and comparing it to the ones on db (key database). The boot process will not finish as expected if they do not have the right certificate/key. The UEFI payloads are:

  • extlinux.conf
  • initrd
  • kernel images (in rootfs, and kernel and recovery partitions)
  • kernel-dtb images (in rootfs, and kernel-dtb and recovery-dtb partitions)
  • BOOTAA64.efi

To enable UEFI Secure Boot, first install the following dependencies:

  • openssl: Needed to generate keys, digital signatures and certificates and use them to sign codes.
  • device-tree-compiler: To compile device tree sources that are going to be used in the boot process to enroll keys for code authentication.
  • efitools: It allows you to generate, sign, and manage cryptographic keys used for UEFI Secure Boot.
  • uuid-runtime: Is a package that provides utilities for generating and managing Universally Unique Identifiers (UUIDs). UUIDs are 128-bit numbers used to uniquely identify information in computer systems.  

To install these dependencies, run the following command:

sudo apt install openssl device-tree-compiler efitools uuid-runtime

Generate RSA key pairs, certificates and EFI signature list File

In order to activate UEFI Secure Boot, first, let's create a folder for the uefi keys inside the Linux for Tegra folder and generate the Platform Key(PK) and certificate:

mkdir uefi_keys

cd uefi_keys

openssl req -newkey rsa:2048 -nodes -keyout PK.key  -new -x509 -sha256 -days 3650 -subj "/CN=my Platform Key/" -out PK.crt
  • -newkey rsa:2048: new RSA private key with a key length of 2048 bits.
  • -nodes: Tells openssl not to encrypt the private key.
  • -keyout PK.key: This specifies the output file for the private key, named PK.key.
  • -new: Creates a new certificate signing request (in this case, used to create a self signed certificate).
  • -x509: tells openssl to generate a self-signed X.509 certificate instead of a CSR (Certificate Signing Request).
  • -sha256: This specifies the SHA-256 hash algorithm for the certificate's signature.
  • -days 3650: Sets the validity period of the certificate to 3650 days (10 years).
  • -subj "/CN=my Platform Key/": Sets the subject of the certificate, which contains information about the certificate's owner.
  • -out PK.crt: This specifies the output file for the certificate, named PK.crt

Generate the PK EFI signature list from X.509 certificate. As reference, the EFI signature list contains the digital signatures in the UEFI bootloader.

cert-to-efi-sig-list -g "${GUID}" PK.crt PK.esl
  • -g "${GUID}": Specifies the GUID (Globally Unique Identifier) that will be associated with the signature list. GUIDs are used to identify different types of signatures and policies in UEFI Secure Boot.
  • PK.crt: This is the input file, which is the X.509 certificate that you want to convert.
  • PK.esl: This is the output file, which will be the EFI signature list. The .esl extension is commonly used for EFI signature lists.

Generate the KEK RSA key and certificate:

openssl req -newkey rsa:2048 -nodes -keyout KEK.key  -new -x509 -sha256 -days 3650 -subj "/CN=my Key Exchange Key/" -out KEK.crt
  • -newkey rsa:2048: new RSA private key with a key length of 2048 bits.
  • -nodes: Tells openssl not to encrypt the private key.
  • -keyout KEK.key: This specifies the output file for the private key, named KEK.key.
  • -new: Creates a new certificate signing request (in this case, used to create a self signed certificate).
  • -x509: tells openssl to generate a self-signed X.509 certificate instead of a CSR (Certificate Signing Request).
  • -sha256: This specifies the SHA-256 hash algorithm for the certificate's signature.
  • -days 3650: Sets the validity period of the certificate to 3650 days (10 years).
  • -subj "/CN= Key Exchange Key/": Sets the subject of the certificate, which contains information about the certificate's owner.
  • -out KEK.crt: This specifies the output file for the certificate, named KEK.crt

Generate the KEK EFI signature list from X.509 certificate.

cert-to-efi-sig-list -g "${GUID}" KEK.crt KEK.esl
  • -g "${GUID}": Specifies the GUID (Globally Unique Identifier) that will be associated with the signature list. GUIDs are used to identify different types of signatures and policies in UEFI Secure Boot.
  • KEK.crt: This is the input file, which is the X.509 certificate that you want to convert.
  • KEK.esl: This is the output file, which will be the EFI signature list. The .esl extension is commonly used for EFI signature lists.

Generate the db_1 RSA Key and certificate.

openssl req -newkey rsa:2048 -nodes -keyout db_1.key  -new -x509 -sha256 -days 3650 -subj "/CN=my Signature Database key/" -out db_1.crt
  • -newkey rsa:2048: new RSA private key with a key length of 2048 bits.
  • -nodes: Tells openssl not to encrypt the private key.
  • -keyout db_1.key: This specifies the output file for the private key, named db_1.key.
  • -new: Creates a new certificate signing request (in this case, used to create a self signed certificate).
  • -x509: tells openssl to generate a self-signed X.509 certificate instead of a CSR (Certificate Signing Request).
  • -sha256: This specifies the SHA-256 hash algorithm for the certificate's signature.
  • -days 3650: Sets the validity period of the certificate to 3650 days (10 years).
  • -subj "/CN= my Signature Database key/": Sets the subject of the certificate, which contains information about the certificate's owner.
  • -out db_1.crt: This specifies the output file for the certificate, named db_1.crt

Generate the db_1 EFI signature list from X.509 certificate.

cert-to-efi-sig-list -g "${GUID}" db_1.crt db_1.esl
  • -g "${GUID}": Specifies the GUID (Globally Unique Identifier) that will be associated with the signature list. GUIDs are used to identify different types of signatures and policies in UEFI Secure Boot.
  • db_1.crt: This is the input file, which is the X.509 certificate that you want to convert.
  • db_1.esl: This is the output file, which will be the EFI signature list. The .esl extension is commonly used for EFI signature lists.

Generate the db_2 RSA Key and certificate.

openssl req -newkey rsa:2048 -nodes -keyout db_2.key  -new -x509 -sha256 -days 3650 -subj "/CN=my another Signature Database key/" -out db_2.crt
  • -newkey rsa:2048: new RSA private key with a key length of 2048 bits.
  • -nodes: Tells openssl not to encrypt the private key.
  • -keyout db_2.key: This specifies the output file for the private key, named db_2.key.
  • -new: Creates a new certificate signing request (in this case, used to create a self signed certificate).
  • -x509: tells openssl to generate a self-signed X.509 certificate instead of a CSR (Certificate Signing Request).
  • -sha256: This specifies the SHA-256 hash algorithm for the certificate's signature.
  • -days 3650: Sets the validity period of the certificate to 3650 days (10 years).
  • -subj "/CN= My another Signature Database/": Sets the subject of the certificate, which contains information about the certificate's owner.
  • -out db_2.crt: This specifies the output file for the certificate, named db_2.crt

Generate the db_2 EFI signature list from X.509 certificate.

cert-to-efi-sig-list -g "${GUID}" db_2.crt db_2.esl
  • -g "${GUID}": Specifies the GUID (Globally Unique Identifier) that will be associated with the signature list. GUIDs are used to identify different types of signatures and policies in UEFI Secure Boot.
  • db_2.crt: This is the input file, which is the X.509 certificate that you want to convert.
  • db_2.esl: This is the output file, which will be the EFI signature list. The .esl extension is commonly used for EFI signature lists.

Output should look like the following:

Generating a RSA private key
.............................+++++
.....+++++
writing new private key to 'PK.key'
-----
Generating a RSA private key
......+++++
.+++++
writing new private key to 'db_2.key'
-----

and you should get three files (.crt .esl .key) for each component:

uefi_keys$ ls
db_1.crt  db_1.key  db_2.esl  KEK.crt  KEK.key  PK.esl  db_1.esl  db_2.crt  db_2.key  KEK.esl  PK.crt   PK.key

Create the UEFI Keys Config File

Open a file named uefi_keys.conf with your preferred text editor and add these lines:

UEFI_DB_1_KEY_FILE="db_1.key";  # UEFI payload signing key
UEFI_DB_1_CERT_FILE="db_1.crt"; # UEFI payload signing key certificate

UEFI_DEFAULT_PK_ESL="PK.esl"
UEFI_DEFAULT_KEK_ESL_0="KEK.esl"

UEFI_DEFAULT_DB_ESL_0="db_1.esl"
UEFI_DEFAULT_DB_ESL_1="db_2.esl"

where:

  • UEFI_DB_1_KEY_FILE and UEFI_DB_1_CERT_FILE are the key and certificate used to sign UEFI payloads
  • UEFI_DEFAULT_PK_ES is the Platform Key EFI Signature list.
  • UEFI_DEFAULT_KEK_ESL_0 is the Key encryption key EFI Signature list.
  • UEFI_DEFAULT_DB_ESL_0 is the EFI Signature List of the signature database (db).
  • UEFI_DEFAULT_DB_ESL_1 is the EFI Signature List of the signature database for known untrusted code signatures (dbx).

Generate the UEFI Secure Boot DTBO

This device tree blob overlay is used to store the security keys in the form of UEFI authenticated variable. It works by adding a modification to the device tree image to generate the UEFI authenticated variable with the database. To generate it, first, move to the Linux for Tegra folder:

cd ..

Use the gen_uefi_keys_dts script to generate it:

sudo tools/gen_uefi_keys_dts.sh uefi_keys/uefi_keys.conf

You should get a similar output to the following:

Linux_for_Tegra$ sudo tools/gen_uefi_keys_dts.sh uefi_keys/uefi_keys.conf
Info: generating default keys dtbo.
Info: adding node PKDefault.
Info: adding node KEKDefault.
Info: adding node dbDefault.
Info: dts file is generated to UefiDefaultSecurityKeys.dts
Info: dtbo file is generated to UefiDefaultSecurityKeys.dtbo
Info: UefiDefaultSecurityKeys.dts and corresponding dtbo are generated.
Info: generating update keys dtbo.
removed 'UefiUpdateSecurityKeys.dts'
Info: no update key dtbo is generated due to no update keys are provided.
Info: generating dtbo is done.

Flash the board with the --uefi_keys option

To activate UEFI Secure Boot, run the flash command with the uefi-keys option. Remember to put the board in recovery mode and connect it to the host. For an NVIDIA Jetson Orin Nano with an SD Card, the command is:

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" --uefi-keys uefi_keys/uefi_keys.conf --showlogs --network usb0 jetson-orin-nano-devkit internal

Now the board has the UEFI Secure Boot activated.