diff --git a/dcs_deploy.py b/dcs_deploy.py index 21e728f..7ab2e9d 100755 --- a/dcs_deploy.py +++ b/dcs_deploy.py @@ -9,6 +9,7 @@ import time from urllib.parse import urlparse import sys as _sys +import getpass dcs_deploy_version = "3.0.0" @@ -209,6 +210,7 @@ def __init__(self): self.selected_config_name = None self.load_db() self.local_overlay_dir = os.path.join('.', 'local', 'overlays') + self.system_encryption_dir = os.path.join(os.getcwd(), 'system_encryption', 'op_tee_tools') if self.args.command != 'list': self.load_selected_config() self.init_filesystem() @@ -243,6 +245,9 @@ def add_common_parser(self, subparser): ab_partition_help = 'Prepare ab partion for system update. Only available for nvme devices' subparser.add_argument('--ab_partition', action='store_true', help=ab_partition_help) + encryption_help = 'Encryption the rootfs partition. Only available for nvme devices' + subparser.add_argument('--encryption', action='store_true', help=encryption_help) + opt_app_size_help = 'Set APP partition size in GB. Use when you get "No space left on device" error while flashing custom rootfs' subparser.add_argument('--app_size', help=opt_app_size_help) @@ -384,11 +389,14 @@ def init_filesystem(self): self.flash_path = os.path.join(self.dsc_deploy_root, 'flash', config_relative_path) self.rootfs_extract_dir = os.path.realpath(os.path.join(self.flash_path, 'Linux_for_Tegra', 'rootfs')) self.l4t_root_dir = os.path.realpath(os.path.join(self.flash_path, 'Linux_for_Tegra')) + self.op_tee_tools_path = os.path.realpath(os.path.join(self.l4t_root_dir, 'source', 'public', 'nvidia-jetson-optee-source.tbz2')) + self.op_tee_tools_dir = os.path.realpath(os.path.join(self.l4t_root_dir, 'source', 'public')) + self.enc_key_path = os.path.realpath(os.path.join(self.l4t_root_dir, 'disk_enc.key')) self.apply_binaries_path = os.path.join(self.l4t_root_dir, 'apply_binaries.sh') self.create_user_script_path = os.path.join(self.l4t_root_dir, 'tools', 'l4t_create_default_user.sh') # generate download resource paths - resource_keys = ["rootfs", "l4t", "nvidia_overlay", "airvolute_overlay", "nv_ota_tools"] + resource_keys = ["rootfs", "l4t", "nvidia_overlay", "airvolute_overlay", "nv_ota_tools", "public_sources"] self.resource_paths = {} for res_name in resource_keys: @@ -445,10 +453,11 @@ def check_dependencies(self): "udev", "uuid-runtime", "whois", "openssl", "cpio", "lz4"] l4t_other_dependencies = ["python-is-python3"] dcs_deploy_dependencies = ["qemu-user-static", "sshpass", "abootimg", "lbzip2", "jq", "coreutils", "findutils" ] + disk_encryption_dependencies = ["python3-cryptography", "python3-cffi-backend", "libxml2-utils", "cryptsetup", "python3-pycryptodome", "python3-crypto", "docker.io"] dependencies = l4t_tool # append dcs_deploy_dependencies which are unique - for dependency in dcs_deploy_dependencies + l4t_other_dependencies: + for dependency in dcs_deploy_dependencies + l4t_other_dependencies + disk_encryption_dependencies: if dependency not in dependencies: dependencies.append(dependency) @@ -588,6 +597,19 @@ def prepare_sources_production(self): print('Applying Nvidia OTA tools ...') ret = self.extract_resource('nv_ota_tools') + if self.get_resource_url('public_sources') != None: + print('Applying Nvidia OP-TEE tools ...') + ret = self.extract_resource('public_sources') + if self.config['l4t_version'] != '62': + ret = extract(self.op_tee_tools_path, self.op_tee_tools_dir) + else: + print(f"Error: OP-TEE tools archive not found at {self.op_tee_tools_path}") + user = getpass.getuser() + cmd_exec(f"sudo mkdir -p '{self.op_tee_tools_dir}'") + cmd_exec(f"sudo chown -R {user}:{user} '{self.op_tee_tools_dir}'") + cmd_exec(f"sudo chmod -R u+rwX '{self.op_tee_tools_dir}'") + ret = extract(os.path.realpath(os.path.join(self.l4t_root_dir, 'source', 'nvidia-jetson-optee-source.tbz2')), self.op_tee_tools_dir) + # Regenerate ssh access in rootfs print("Purging ssh keys, this part needs sudo privilegies:") cmd_exec("/usr/bin/sudo /usr/bin/id > /dev/null") @@ -743,10 +765,65 @@ def load_selected_config(self): self.selected_config_name = config + + def update_num_sectors(self, file_path): + try: + if self.config['l4t_version'] != '62': + cmd = f"sudo sed -i 's/NUM_SECTORS/250069680/g' {file_path}" + else: + cmd = f"sudo sed -i 's/EXT_NUM_SECTORS/250069680/g' {file_path}" + ret = cmd_exec(cmd, print_command=True) + if ret != 0: + print(f"Failed to update NUM_SECTORS in {file_path}. Command returned {ret}.") + exit(1) + print(f"Updated NUM_SECTORS in {file_path} to 250069680.") + except Exception as e: + print(f"Failed to update NUM_SECTORS in {file_path}: {str(e)}") + exit(1) + + def copy_op_tee_tools(self, destination_dir): + + if not os.path.exists(self.system_encryption_dir): + print(f"Source directory does not exist: {self.system_encryption_dir}") + exit(1) + + if not os.path.exists(destination_dir): + print(f"Destination directory does not exist: {destination_dir}. Creating it...") + os.makedirs(destination_dir) + + try: + print(f"Copying {self.system_encryption_dir} to {destination_dir}...") + cmd_exec(f"sudo cp -r {os.path.join(self.system_encryption_dir, '*')} {destination_dir}", print_command=True) + print("Copy completed successfully.") + except Exception as e: + print(f"Failed to copy op_tee_tools: {str(e)}") + exit(1) + + def generate_encryption_key(self): + gen_ekb_dir = os.path.relpath('source/public/optee/samples/hwkey-agent/host/tool/gen_ekb/') + self.copy_op_tee_tools(destination_dir = gen_ekb_dir) + os.chdir(gen_ekb_dir) + try: + if self.config['l4t_version'] != '62': + script = os.path.join(os.getcwd(), "generate_keys.sh") + else: + script = os.path.join(os.getcwd(), "generate_keys_jp6_2.sh") + cmd_exec(f"sudo chmod +x '{script}'", print_command=True) + ret = cmd_exec(f"sudo bash '{script}'", print_command=True) + ret = cmd_exec(f"sudo cp eks_t234.img {self.l4t_root_dir}/bootloader/eks_t234.img", print_command=True) + ret = cmd_exec(f"sudo cp eks_t234.img {self.l4t_root_dir}/bootloader/eks.img", print_command=True) + ret = cmd_exec(f'sudo cp sym2_t234.key {self.enc_key_path}', print_command=True) + os.chdir(self.l4t_root_dir) + except Exception as e: + print(f"Failed to generate encryption key: {str(e)}") + os.chdir(self.l4t_root_dir) + exit(1) + def setup_initrd_flashing(self): os.chdir(self.l4t_root_dir) #set variables for initrd flash self.flash_script_path = os.path.relpath('tools/kernel_flash/l4t_initrd_flash.sh') + self.tegrarcm_v2_path = os.path.relpath('bootloader/tegrarcm_v2') if self.config['l4t_version'] != '62': if self.config['device'] == 'xavier_nx': @@ -776,9 +853,15 @@ def setup_initrd_flashing(self): elif self.config['storage'] == 'nvme': self.rootdev = "external" self.external_device = "--external-device nvme0n1p1 " - if self.args.ab_partition == True: + if self.args.ab_partition and self.args.encryption: + # setup multiple app partitions with encryption + self.ext_partition_layout = os.path.relpath('tools/kernel_flash/flash_l4t_nvme_rootfs_ab_enc.xml') + elif self.args.ab_partition: # setup multiple app partitions self.ext_partition_layout = os.path.relpath('tools/kernel_flash/flash_l4t_nvme_rootfs_ab.xml') + elif self.args.encryption: + # setup single partition with encryption + self.ext_partition_layout = os.path.relpath('tools/kernel_flash/flash_l4t_nvme_rootfs_enc.xml') else: # setup no multiple app partitions self.ext_partition_layout = os.path.relpath('tools/kernel_flash/flash_l4t_external_custom.xml') @@ -805,9 +888,15 @@ def setup_initrd_flashing(self): self.rootdev = "external" self.external_device = "" self.external_device = "--external-device nvme0n1p1 " - if self.args.ab_partition == True: + if self.args.ab_partition and self.args.encryption: + # setup multiple app partitions with encryption + self.ext_partition_layout = os.path.relpath('tools/kernel_flash/flash_l4t_nvme_rootfs_ab_enc.xml') + elif self.args.ab_partition: # setup multiple app partitions self.ext_partition_layout = os.path.relpath('tools/kernel_flash/flash_l4t_nvme_rootfs_ab.xml') + elif self.args.encryption: + # setup single partition with encryption + self.ext_partition_layout = os.path.relpath('tools/kernel_flash/flash_l4t_nvme_rootfs_enc.xml') else: # setup no multiple app partitions self.ext_partition_layout = os.path.relpath('tools/kernel_flash/flash_l4t_t234_nvme.xml') @@ -821,8 +910,8 @@ def setup_initrd_flashing(self): def generate_images(self): self.prepare_status.change_group("images") - # check commandline parameter if they are same as previous and images are already generated skip generation - if self.prepare_status.is_identifier_same_as_prev(["--regen", "--force"]) and self.prepare_status.get_status() == True: + # check commandline parameter if they are same as previous and images are already generated skip generation + if self.prepare_status.is_identifier_same_as_prev(["--regen", "--force"]) and self.prepare_status.get_status() == True and not self.args.encryption: print("Images already generated! Skipping generating images!") return 0 @@ -838,12 +927,12 @@ def generate_images(self): # flash external nvme drive elif self.config['storage'] == 'nvme': #file to check: initrdflashparam.txt - contains last enterred parameters - env_vars = "" + env_vars = [] opt_app_size_arg = "" external_only = "--external-only" # flash only external device if self.args.ab_partition == True: - env_vars = "ROOTFS_AB=1" + env_vars.append("ROOTFS_AB=1") if self.args.rootfs_type == "minimal": opt_app_size = 4 else: @@ -852,6 +941,9 @@ def generate_images(self): external_only = "" # flash internal and external device #self.rootdev = "external" # set UUID device in kernel commandline: rootfs=PARTUUID= + if self.args.encryption == True: + env_vars.append("ROOTFS_ENC=1") + if self.args.app_size is not None: opt_app_size_arg = f"-S {self.args.app_size}GiB" @@ -859,8 +951,17 @@ def generate_images(self): external_only = "" # don't flash only external device cmd_exec("pwd") - ret = cmd_exec(f"sudo {env_vars} ./{self.flash_script_path} {opt_app_size_arg} --no-flash {external_only} {self.external_device} " + - f"-c {self.ext_partition_layout} {self.orin_options} --showlogs {self.board_name} {self.rootdev}", print_command=True) + self.update_num_sectors(self.ext_partition_layout) + env_vars_str = " ".join(env_vars) + if self.args.encryption == True: + self.generate_encryption_key() + ret = cmd_exec(f"sudo ./{self.tegrarcm_v2_path} --new_session --chip 0x23 --uid | sudo tee ECID.key > /dev/null", print_command=True) + ret = cmd_exec(f"sudo ./{self.flash_script_path} --showlogs {self.orin_options} -i {self.enc_key_path} --no-flash {self.board_name} {self.rootdev}", print_command=True) + ret = cmd_exec(f"sudo {env_vars_str} ./{self.flash_script_path} {opt_app_size_arg} --no-flash {external_only} {self.external_device} -i {self.enc_key_path} " + + f"-c {self.ext_partition_layout} --showlogs --external-only --append --network usb0 {self.board_name} {self.rootdev}", print_command=True) + else: + ret = cmd_exec(f"sudo {env_vars_str} ./{self.flash_script_path} {opt_app_size_arg} --no-flash {external_only} {self.external_device} " + + f"-c {self.ext_partition_layout} {self.orin_options} --showlogs {self.board_name} {self.rootdev}", print_command=True) self.prepare_status.set_status(ret, last_step= True) return ret diff --git a/local/config_db.json b/local/config_db.json index 59d5814..78f8884 100644 --- a/local/config_db.json +++ b/local/config_db.json @@ -77,6 +77,7 @@ "l4t_version": "512", "nvidia_overlay": "none", "local_overlays": ["dcs_first_boot", "hardware_support_layer", "save_version.sh"], + "public_sources": "https://developer.nvidia.com/downloads/embedded/l4t/r35_release_v4.1/sources/public_sources.tbz2", "airvolute_overlay": "https://airvolute.com/download/dcs_deployment_testing/airvolute_overlay_jp512_ao_03_dcs20.tbz2", "l4t": "https://developer.download.nvidia.com/embedded/L4T/r35_Release_v4.1/release/Jetson_Linux_R35.4.1_aarch64.tbz2", "nv_ota_tools" : "https://developer.nvidia.com/downloads/embedded/l4t/r35_release_v4.1/release/ota_tools_r35.4.1_aarch64.tbz2", @@ -104,7 +105,8 @@ "board_expansion": "default", "local_overlays": ["dcs_first_boot", "hardware_support_layer", "save_version.sh"], "nvidia_overlay": "none", - "airvolute_overlay": "https://airvolute.com/download/dcs_deploy/airvolute_overlay-JP6.2-AO-0.1.6.tbz2", + "public_sources": "https://developer.nvidia.com/downloads/embedded/l4t/r36_release_v4.3/sources/public_sources.tbz2", + "airvolute_overlay": "https://storage.googleapis.com/dcs-deploy-overlays/airvolute_overlay_52451f69.tbz2", "l4t": "https://developer.nvidia.com/downloads/embedded/l4t/r36_release_v4.3/release/Jetson_Linux_r36.4.3_aarch64.tbz2", "nv_ota_tools" : "", "rootfs": "https://developer.nvidia.com/downloads/embedded/l4t/r36_release_v4.3/release/Tegra_Linux_Sample-Root-Filesystem_r36.4.3_aarch64.tbz2", diff --git a/readme.md b/readme.md index 6d0ba41..ad6a752 100644 --- a/readme.md +++ b/readme.md @@ -146,6 +146,19 @@ As a root of this filesystem, `.dcs_deploy` folder is created inside **host pc H - `download` contains downloaded archives needed for flashing - `flash` contains extracted folders that are needed for flashing. Those are folders from `download` dir + some nvidia and airvolute scripts applied, so the flashing environment is fully ready. +### Disk encryption +The `--encryption` option enables encryption for the root filesystem (rootfs) partition. This feature is available only for devices using `nvme` storage. + +- Automatically generates encryption keys (`sym2_t234.key`, `oem_k1.key`, etc.) during the flashing process. +- Supports combinations with the `--ab_partition` option for creating multiple encrypted partitions. +- Ensures secure storage of sensitive data on the device. + +- **Storage**: The device must use `nvme` as the storage medium. +- **Dependencies**: Ensure the following tools are installed: + - `cryptsetup` + - `openssl` + - `docker.io` + ### Local overlays To add features easily to the device without need of the creation of a new rootfs it is possible to utilize local overlays. These overlays are processed during the run of the `dcs_deploy`. diff --git a/system_encryption/op_tee_tools/Dockerfile b/system_encryption/op_tee_tools/Dockerfile new file mode 100644 index 0000000..8e328b4 --- /dev/null +++ b/system_encryption/op_tee_tools/Dockerfile @@ -0,0 +1,15 @@ +# Use an official Python image as the base +FROM python:3.12-slim + +# Set the working directory inside the container +WORKDIR /app + +# Copy the script and dependencies into the container +COPY gen_ekb.py /app/ +COPY requirements.txt /app/ + +# Install required Python packages +RUN pip install --no-cache-dir -r requirements.txt + +# Set the default command to run the script +ENTRYPOINT ["/usr/local/bin/python3", "gen_ekb.py", "-chip", "t234", "-oem_k1_key", "oem_k1.key", "-fv", "fv_ekb_t234", "-in_sym_key", "sym_t234.key", "-in_sym_key2", "sym2_t234.key", "-out", "eks_t234.img"] \ No newline at end of file diff --git a/system_encryption/op_tee_tools/DockerfileJp62 b/system_encryption/op_tee_tools/DockerfileJp62 new file mode 100644 index 0000000..b6aa866 --- /dev/null +++ b/system_encryption/op_tee_tools/DockerfileJp62 @@ -0,0 +1,15 @@ +# Use an official Python image as the base +FROM python:3.12-slim + +# Set the working directory inside the container +WORKDIR /app + +# Copy the script and dependencies into the container +COPY gen_ekb.py /app/ +COPY requirements.txt /app/ + +# Install required Python packages +RUN pip install --no-cache-dir -r requirements.txt + +# Set the default command to run the script +ENTRYPOINT ["/usr/local/bin/python3", "gen_ekb.py", "-chip", "t234", "-oem_k1_key", "oem_k1.key", "-in_auth_key", "auth_t234.key", "-in_sym_key", "sym_t234.key", "-in_sym_key2", "sym2_t234.key", "-out", "eks_t234.img"] \ No newline at end of file diff --git a/system_encryption/op_tee_tools/generate_keys.sh b/system_encryption/op_tee_tools/generate_keys.sh new file mode 100755 index 0000000..7f93678 --- /dev/null +++ b/system_encryption/op_tee_tools/generate_keys.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +# [T234 example] +# Fill your OEM_K1 fuse key value +echo "2d4a614e645267556b58703273357638792f423f4428472b4b6250655368566d" > oem_k1.key + +# This is the fixed vector for deriving EKB root key from fuse. +# It is expected user to replace the FV below with a user specific +# FV, and code the exact same user specific FV into OP-TEE. +echo "bad66eb4484983684b992fe54a648bb8" > fv_ekb_t234 + +# Generate user-defined symmetric key files +openssl rand -rand /dev/urandom -hex 32 > sym_t234.key # kernel/kernel-dtb encryption key +openssl rand -rand /dev/urandom -hex 16 > sym2_t234.key # disk encryption key + + +# Build the Docker image +docker build -t gen-ekb-tool . +# Run the container with arguments +docker run --rm -v $(pwd):/app gen-ekb-tool \ No newline at end of file diff --git a/system_encryption/op_tee_tools/generate_keys_jp6_2.sh b/system_encryption/op_tee_tools/generate_keys_jp6_2.sh new file mode 100755 index 0000000..0d46cac --- /dev/null +++ b/system_encryption/op_tee_tools/generate_keys_jp6_2.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +# [T234 example] +# Fill your OEM_K1 fuse key value +echo "0000000000000000000000000000000000000000000000000000000000000000" > oem_k1.key + +# This is the fixed vector for deriving EKB root key from fuse. +# It is expected user to replace the FV below with a user specific +# FV, and code the exact same user specific FV into OP-TEE. +echo "0000000000000000000000000000000000000000000000000000000000000000" > sym_t234.key + +# Generate user-defined symmetric key files +openssl rand -rand /dev/urandom -hex 16 > auth_t234.key # kernel/kernel-dtb encryption key +openssl rand -rand /dev/urandom -hex 16 > sym2_t234.key # disk encryption key + + +# Build the Docker image +docker build -t gen-ekb-tool -f DockerfileJp62 . +# Run the container with arguments +docker run --rm -v $(pwd):/app gen-ekb-tool \ No newline at end of file diff --git a/system_encryption/op_tee_tools/requirements.txt b/system_encryption/op_tee_tools/requirements.txt new file mode 100644 index 0000000..a25ca95 --- /dev/null +++ b/system_encryption/op_tee_tools/requirements.txt @@ -0,0 +1,2 @@ +pycryptodome +cryptography \ No newline at end of file