Disk Encryption

Applies to: Jetson Xavier NX series, Jetson AGX Xavier series, and Jetson TX2 series
Disk encryption encrypts a whole disk or partition to protect the data it contains. Jetson Linux offers disk encryption that is based on Linux Unified Key Setup (LUKS) Data-at-rest encryption, the standard for Linux disk encryption. It provides a standard disk format that stores all necessary setup information on the disk itself, in the partition header. The passphrase in the Trusty architecture supports disk encryption functionality with multiple user passwords.

Setup Preparation

Jetson Linux uses cryptsetup, a LUKS userspace command line utility, to set up and unlock an encrypted disk. It uses the DMCrypt kernel module as its backend. The utility sets up the encrypted disk as a LUKS partition and configures it with a passphrase.
The DMCrypt kernel module is the standard device-mapper interface for encryption functionality in the Linux kernel. It sits between the disk driver and the file system, where it encrypts and decrypts data blocks transparently.
For more information about cryptsetup and DMCrypt, see the cryptsetup project and the DMCrypt documentation, both hosted by GitLab.

Details of Operation

Two types of keys are used for disk encryption:
The master key, also known as the Disk Encryption Key (DEK). This key is used for data encryption or decryption when data is transferred between the filesystem and the disk.
The master key is generated by cryptsetup. The default and recommended source for data used to generate the key is /dev/random. The key resides in the LUKS header, and is encrypted by the key derived from the passphrase.
The passphrase or password is an input string or pattern supplied by the user to set up disk encryption and lock the disk. The same input is used to decrypt and unlock data stored on the disk.
The passphrase is generated with help from two applications:
luks-srv: A trusted application (TA) that queries the EKB key and uses it to derive a LUKS key, which is the root key for deriving the passphrase.
nvluks-srv-app: A client application (CA) in the normal (non-secure) world that communicates with luks-srv to retrieve the passphrase. cryptsetup uses the passphrase to unlock the encrypted disk.
The cryptographic algorithm used for disk encryption is AES-CBC with ESSIV, use a key length of 128 bits. This mode of operation makes the encrypted data look completely random, minimizing the potential for attack. The entire key derivation process is performed in the secure world. The following diagram outlines the process:
Diagram Description automatically generated
The generation process uses this key derivation function (KDF) to generate a per-device unique LUKS key or a generic key:
NIST-SP-800-108 is a key derivation function (KDF) recommended by the National Institute of Standards and Technology algorithm for key derivation using pseudorandom functions..
in-key is a string from which the key is derived. In the L4T implementation it is the EKB key.
label-string identifies the purpose of the key to be generated. In the reference implementation label-string is a fixed value defined by the TA.
context-string is a string that contains information related to the derived key, and which the KDF uses as a parameter for key generation. In the reference implementation the default context string is the disk’s UUID, but you can change this by modifying the TA.
To generate a per-device unique LUKS key, label-string is "luks-srv-ecid" and context‑string is "$(ECID)" (a reference to the environment variable ECID).
To generate a generic key, label-string is "luks-srv-generic", and context‑string is "generic-key".
Once the LUKS key is created, the nvluks-srv-app CA from the normal world can query the luks-srv TA to obtain the passphrase.
Diagram Description automatically generated
The nvluks-srv-app CA communicates with the TA by sending it an IPC packet. This is the packet’s structure:
typedef struct luks_srv_cmd_msg {
uint32_t luks_srv_cmd;
char context_str[40];
char output_passphrase[16];
} luks_src_cmd_msg_t;
The packet’s data members are:
luks_srv_cmd is the command message that tells the TA what to do with the packet. LUKS_GET_UNIQUE_PASS tells the TA to generate a per-device unique passphrase; LUKS_GET_GENERIC_PASS tells it to generate a generic one.
context_str is the context string passed to the KDF to generate the passphrase. In this implementation the input context string is the disk UUID, so no user interaction is needed to create the encrypted disk image or to unlock it at boot time.
output_passphrase is the passphrase returned by the TA. With the output passphrase from nvluks-srv-app, it can be used to unlock the encrypted disk.
This example shows how to unlock the encrypted disk with nvluks-srv-app.
# Using "cryptsetup" and "nvluks-srv-app" to unlock the encrypted device:
nvluks-srv-app --context-string "${UUID}" \
--get-unique-pass | \
cryptsetup -c aes-cbc-essiv:sha256 \
-s 128 \
luksOpen \
<luksDevice> <DM_name>
To prevent any form of attack from extracting the passphrase at boot time (e.g. in the initrd), use the luks_srv_cmd command type LUKS_NO_PASS_RESPONSE. This command instructs luks-srv not to respond to a LUKS_GET command (to get the passphrase) until reboot. This step is crucial to ensure the security of your design.

The Threat Model

The purpose of disk encryption is to prevent an attack from stealing or tampering with data on the disk. Even if the disk is physically unmounted (or, in the case of an internal device such as an eMMC, is removed from the device), the data cannot be exposed.
Disk encryption cannot protect against the following types of threat:
A background process or daemon that has a security hole. An attacker may be able to use the hole to gain control of the process and access the disk.
Theft or leakage of the login ID and password. An attacker can use these credentials to log in to the device and access the disk.

Disk Encryption Implementation in Jetson Linux

Jetson Linux provides a reference implementation of disk encryption which fulfills the security requirements of many use cases. If your use case’s requirements are different, you can modify it or use it as a model for implementing your own.
Layout of an Encrypted Disk
Because the bootloader cannot read encrypted files, disk encryption requires L4T to divide a “naïve” system’s APP partition in two:
The unencrypted APP partition contains the /boot branch of the filesystem, including the kernel, DTB, and initrd images
A new, encrypted APP_ENC partition contains the rest of the filesystem.
Following is an example of the APP partition definition for a system on which disk encryption is not enabled. You can find a definition like this one in the L4T BSP partition configuration files, e.g. Linux_for_Tegra/‌bootloader/‌t186ref/cfg/‌flash_t194_sdmmc.xml for a Jetson AGX Xavier™ series device booting from SDMMC memory.
<partition name="APP" type="data">
<allocation_policy> sequential </allocation_policy>
<filesystem_type> basic </filesystem_type>
<size> APPSIZE </size>
<file_system_attribute> 0 </file_system_attribute>
<allocation_attribute> 0x8 </allocation_attribute>
<align_boundary> 4096 </align_boundary>
<percent_reserved> 0 </percent_reserved>
<filename> APPFILE </filename>
<unique_guid> APPUUID </unique_guid>
**Required.** Contains the rootfs. This partition must be defined
after `primary_GPT` so that it can be accessed as the fixed known
special Device `/dev/mmcblk0p1`.
The following example shows how disk encryption separates APP into two partitions. You can find the file in the L4T BSP, e.g. Linux_for_Tegra/‌bootloader/‌t186ref/cfg/‌flash_t194_sdmmc_enc_rfs.xml for the same device as above.
<partition name="APP" type="data">
<allocation_policy> sequential </allocation_policy>
<filesystem_type> basic </filesystem_type>
<size> 104857600 </size>
<file_system_attribute> 0 </file_system_attribute>
<allocation_attribute> 0x8 </allocation_attribute>
<align_boundary> 4096 </align_boundary>
<percent_reserved> 0 </percent_reserved>
<filename> system_boot.img </filename>
<unique_guid> APPUUID </unique_guid>
**Required.** Contains the boot partition. This partition must be
defined after `primary_GPT` so that it can be accessed as the fixed
known special device `/dev/mmcblk0p1`.
<partition name="APP_ENC" type="data" encrypted=”true”>
<allocation_policy> sequential </allocation_policy>
<filesystem_type> basic </filesystem_type>
<size> APP_ENC_SIZE </size>
<file_system_attribute> 0 </file_system_attribute>
<allocation_attribute> 0x8 </allocation_attribute>
<align_boundary> 4096 </align_boundary>
<percent_reserved> 0 </percent_reserved>
<filename> system_root_encrypted.img </filename>
<unique_guid> APP_ENC_UUID </unique_guid>
**Required.** Contains the encrypted root partition.
The differences between the first example’s definition of APP and the second example’s definitions of APP and APP_ENC are highlighted by red type. The elements in the APP_ENC definition are the same as those in APP.
Notice that the <size> element of APP specifies an actual number, but the <size> element of APP_ENC specifies a symbol, APP_ENC_SIZE. Later, the value of APP_ENC_SIZE must be calculated by subtracting the size of APP from the total rootfs size.
Each partition’s <filename> element specifies the actual file name of the appropriate disk image (not a symbol that is resolved to the file name during flashing).
The partitions’ <unique_guid> elements specify the symbols APPUUID and APP_ENC_UUID respectively. Both symbols are translated to real UUID numbers by the image generation process.
The APP_ENC partition’s encrypted attribute indicates that the partition is encrypted.
With the new partition layout, new parameters are needed in the board configuration file to enable disk encryption and apply the new partition layout file. Use the appropriate board configuration file for your device, e,g, Linux_for_Tegra/p2972-0000.conf.common for a Jetson AGX Xavier module in a Jetson AGX Xavier Developer Kit. The disk_enc_enable setting indicates that disk encryption is enabled, and EMMC_CFG identifies the partition layout file to use:
The flash tool uses the board configuration file to generate file system images and flash them onto the device.
How to Create File System Images
When you create file system images to support booting with an encrypted disk, keep the following points in mind:
The bootarg kernel command line’s root parameter must use the UUID to identify the root disk to boot from an encrypted root disk.
The crypttab file in the initrd includes the mount point and the device name of the encrypted disk. It may be a real device name like /dev/mmcblk0p1 or the UUID of the disk.
The fstab file in the rootfs is updated with the /boot directory in the APP partition. This helps the automount process to mount the partition at boot time.
How to Create an Encrypted Rootfs on the Host
The rootfs is generated on the host by flash.sh. The diagram below shows the elements of the rootfs (in green), the utilities used to generate it (in blue), and the input (in red).
The input has two parts: the plain key file of the EKB key used for disk encryption, and an input string used to generate the passphrase. By default, the input string is the UUID of the encrypted disk. You can modify the script that generates the rootfs to let user to enter their own string. You must change the initrd accordingly to make it use the user-supplied string.
You must generate the rootfs on a secure system, that is, a secure host computer equipped with a Hardware Security Module (HSM). The HSM is used for key generation and management to secure key assets and safe transport to the factory floor. This is necessary to ensure that the keys cannot be leaked to an unsecure system on the production line. flash.sh invokes two helper scripts to generate the passphrase and the disk images:
gen_luks_passphrase.py follows the same process as the hwkey-agent and luks-srv TAs to derive the LUKS key, and uses the key to generate the passphrase.
disk_encryption.sh outputs the disk images for the initrd, system_boot.img, and system_root_encrypted.img.
The Jetson Linux reference implementation only generates per-device encrypted disk images. Use it as a starting point to develop a script that is suited to your use case and production environment.
Following is an example of commands that use flash.sh:
# the disk encryption key in the EKB partition
$ echo "96cdb5da247b37bb536e9f5506d37e52" > ekb.key
$ sudo ROOTFS_ENC=1 ./flash.sh -i “./ekb.key” <board> <rootdev>
The flash.sh command line switch ‑‑i specifies the key to be used for disk encryption. The reference implementation described above uses the EKB key. You can customize the script to use a different key source. If you do so, review the overall key derivation and passphrase generation flow accordingly to make everything works correctly.
How to Flash an Encrypted Rootfs to an External Storage Device
To flash an encrypted rootfs generated by flash.sh to an external storage device, use l4t_initrd_flash.sh (also known as “the initrd flashing tool”). (See the section Flashing with initrd in the topic Flashing and Booting the Target Device).
Following is an example of commands that use l4t_initrd_flash.sh to flash an encrypted rootfs to an NVMe SSD attached to a Jetson AGX Xavier series device:
$ cd Linux_for_Tegra
$ sudo ROOTFS_ENC=1 ./tools/kernel_flash/l4t_initrd_flash.sh --external-device nvme0n1p1 \
> -c ./tools/kernel_flash/flash_l4t_nvme_rootfs_enc.xml --external-only -S 8GiB \
> jetson-xavier external
Tools and instructions for flashing an encrypted rootfs with initrd may be found in the directory /Linux_for_Tegra/tools/kernel_flash/. For more detailed information, see README_initrd_flash.txt in that directory.
To Enhance initrd to Unlock an Encrypted Rootfs
To boot from an encrypted root filesystem, you need an initrd image which includes the necessary utilities (e.g. cryptsetup) and scripts to set up the root device after the kernel is initialized, but before the rest of the operating system is booted. The image must contain the APP partition, which contains the /boot branch of the rootfs (unencrypted), and the APP_ENC partition, which contains the rest of the rootfs (encrypted).
The boot flow diagram below shows that by default the bootloader (CBoot) uses the APP partition as the boot partition. U‑Boot checks the first partition after the primary GPT, which is the APP partition as well. Thus, the partition layout for an encrypted disk works for either bootloader.)
The init script in the initrd follows these steps to check the encrypted root device, unlock it, and mount it:
1. Verify that the root parameter specifies the same device as the root device setting in /etc/crypttab.
2. Verify that the root device is a LUKS device.
3. Unlock the encrypted root device with the per-device unique passphrase.
4. Mount the root device.
Command the luks-srv TA not to respond to further passphrase requests until reboot.
To modify initrd to unlock additional encrypted file systems
The file /etc/crypttab, kept in initrd, describes encrypted block devices that are set up during system boot. Each line of this file has the form:
<volume-name> UUID=<uuid>
<volume-name> is the name of a volume in which decrypted data is to be placed. Its block device is set up in /dev/mapper/. <volume-name> must be unique across all lines in the file.
<uuid> is the UUID of the underlying block device containing encrypted data.
Here are two examples of entries in /etc/crypttab:
crypt_root UUID=b5600ed6-69e7-42b8-bee3-ecfdd12649d1
crypt_UDA UUID=cf6fa01d-1127-4612-9992-2f6db77385e0
If your device has several encrypted file systems, you must add a line to /etc/crypttab for each one you want Bootloader to unlock.
This is an example of how to unlock an encrypted file system:
1. Enter this command to find the UUID of the encrypted file system:
$ sudo blkid | grep TYPE=\"crypto_LUKS\"
This command line displays a line of output for each encrypted disk that includes the disk’s UUID. Such a line looks like this:
/dev/mmcblk0p43: UUID="5096aa4d-6590-429b-9295-a1fe041b8fa3" TYPE="crypto_LUKS" PARTLABEL="UDA" PARTUUID="2b23da7f-2f18-44bf-9e1d-6e3a3a39ad21"
2. Unpack the initrd. For instructions, see the section Modifying Jetson RAM Disk.
3. Add a line to /etc/crypttab for each encrypted file system that you want to unlock at subsequent reboots. The line specifies the UUID of the file system to be unlocked, as in this example:
crypt_fs UUID="5096aa4d-6590-429b-9295-a1fe041b8fa3"
4. Repackage the initrd and store it in /boot, replacing the initrd that was originally there.
5. Create a subdirectory in /mnt in which initrd can mount each unlocked file system during the initialization process. For the sake of simplicity, NVIDIA recommends giving each subdirectory the same name as the volume to be mounted there. For the volume specified in step 3, the subdirectory would be named crypt_fs:
$ mkdir -p /mnt/crypt_fs
Bootloader unlocks your encrypted file system at each subsequent reboot.
The following diagram shows the overall flow of the encrypted disk unlocking process.
The steps of the process are:
1. nvluks-srv-app queries the hardware-based passphrase from the Trusty luks-srv TA and pass it to the cryptsetup utility.
2. cryptsetup unlocks the disk and decrypts the disk encryption key (DEK) with the passphrase.
3. cryptsetup invokes the DMCrypt kernel module and loads the key into the kernel. DMCrypt uses the tegra-crypto driver, which uses Security Engine (SE) hardware for data encryption and decryption.
4. Subsequent disk I/O is routed through DMCrypt, which decrypts and encrypts data as it reads and writes.

Manufacturing process

The following diagram illustrates the post-development manufacturing process for a Jetson-based device:
All disk images and blobs to be flashed onto production devices must be generated on a secure system. The HSM holds all of the security keys needed in the manufacturing process. If more than one production device is to be flashed at a time, the security machine must be able to deploy images to other systems (flashing machines) for flashing devices.
If the device is designed to use per-device encrypted disk images, the images must be generated one at a time on the security machine.