Skip to main content
Version: Torizon OS 7.x.y

Production Programming Secure Boot with Torizon

Introduction

This article will go over how to program your devices at scale for secure boot on Torizon OS based systems. The article will cover two prominent use-cases.

The first use-case will be how to create an image for production programming that will automatically fuse the device on first boot.

The second use-case will be how to update a system already in the field to be secure-boot enabled with the fuses set.

This article complies with the Typographic Conventions for the Toradex Documentation.

info

Currently production programming for secure boot is only supported on devices that use HAB/AHAB (high assurance boot/advanced high assurance boot), as their secure boot technology. This includes the following devices:

  • Colibri i.MX6DL
  • Colibri i.MX6ULL 1GB
  • Colibri i.MX7D 1GB
  • Colibri i.MX8X
  • Apalis i.MX6
  • Apalis i.MX8
  • Verdin i.MX8M Mini
  • Verdin i.MX8M Plus

Prerequisites

In order to perform production programming of secure boot in Torizon OS, the following is required.

For both factory and in-field programming:

  • A Torizon OS image of at least version 7.2.0.
  • TorizonCore Builder installed on your host machine. Must be a version of at least 3.13.0.
  • The public key information that will be fused to the device. This should match the keys used to create the secure boot enabled Torizon OS image that you will use for programming.
    • Building a secure boot image via Yocto will result in this key information being available in the deploy directory of your build. For example:
$ cat deploy/images/verdin-imx8mp/fuse-cmds.txt
# These are One-Time Programmable e-fuses. Once you write them you can't
# go back, so get it right the first time!
fuse prog -y 6 0 0x8AE322B2
fuse prog -y 6 1 0xDF2939A3
fuse prog -y 6 2 0x9DA80323
fuse prog -y 6 3 0x3B024EF2
fuse prog -y 7 0 0xA53091
fuse prog -y 7 1 0x55304E7A
fuse prog -y 7 2 0xFB8FF259
fuse prog -y 7 3 0x9CE57582

# After the device successfully boots a signed image without generating
# any HAB events, it is safe to secure, or 'close', the device. This is
# the last step in the process. Once the fuse is blown, the chip does
# not load an image that has not been signed using the correct PKI tree.
# Be careful! This is again a One-Time Programmable e-fuse. Once you
# write it you can't go back, so get it right the first time. If
# anything in the previous steps wasn't done correctly, after writing
# this bit, the SOM will not boot anymore!
fuse prog -y 1 3 0x02000000
  • The hex values are what is needed for this process. The hex value for the closing operation is not needed, since this is constant only depending on the SoC. Devices using HAB should have 8 hex values, while devices using AHAB have 16. Make sure to double-check these values are correct for your setup, as there is no way to confirm this during the production programming process.

Additional prerequisites, for in-field programming:

caution

Before proceeding please take note that setting the fuse values on any device is a one-time operation and can not be reversed. Take care to make sure your information is correct and the instructions are understood before committing to any action.

It is also recommend to test this on a singular device, before scaling it to multiple devices.

Fuse Yaml File

For both factory and in-field programming at scale, the process will also require a yaml file describing the public key information that will be fused to the device. The yaml file should look like so:

# This is just an example DO NOT COPY THE VALUES HERE
fuses:
fuse-val1: 0x1A
fuse-val2: 0x2B
fuse-val3: 0x3C
fuse-val4: 0x4D
fuse-val5: 0x5E
fuse-val6: 0x6F
fuse-val7: 0x7A
fuse-val8: 0x8B
fuse-close: False

The fields are fairly self-explanatory, but just to be clear:

  • fuse-valX: This field contains the public key hex value for the Xth numbered fuse. Again make sure these values are correct and are in the proper order. For devices supporting AHAB this goes up to fuse-val16.
  • fuse-close: This field contains a boolean value. This controls whether the final closing fuse should be set as well.
    • True means the device will be closed and will only boot with a properly signed bootloader.
    • False means the device will not be closed and can still boot unsigned bootloaders. The other 8/16 fuses containing the public key information will still be fused even if this is false.

This file will be used as an input to TorizonCore Builder for various commands. If the yaml file you provide does not adhere to the expected format the tool will try to detect this and throw an error to allow you to correct the file.

Factory Programming

This section assumes you already have a Torizon OS image with secure boot enabled, as described prior. This means the image contains the logic necessary to perform the secure boot fusing on first boot, as well as the bootloader of this image is appropriately signed. If using an image without the proper secure boot configurations enabled, this process will do nothing and the device will not be fused on first boot.

Using your Torizon OS image and the yaml file described in "Fuse Yaml File". You can run the following TorizonCore Builder command:

$ torizoncore-builder ubootenv fuses <path to input Torizon OS image> <path to where the output image will be created> --fuse-file <path to fuse yaml file>

If successful, a new Torizon OS image will be created in the path specified. The image can be installed with Toradex Easy Installer as usual. Please note that the output of this command can not be installed via the deploy command of Toradex Easy Installer. For the changes related to secure boot fusing to take effect, the OS image must be flashed in full.

Once this image is flashed the following will happen:

  • On first boot into the image the boot script will fuse the 8/16 fuse values to the corresponding fuse locations.
  • The device will reboot again as this is required for the secure boot fuses to take full effect after the initial fusing.
  • The device will then be closed or not, depending on what was given in the fuse-close field of your yaml file.
    • If there was an error detected while trying to set the prior 8/16 fuses, then the fusing logic will avoid closing the device as to not brick the device, regardless of the value of fuse-close.
    • Additionally, if the hab/ahab_status commands were to return an event, which usually indicates the device should not be closed, then the fusing logic will also not close the device in this case.
  • The device should then finish booting into the OS proper.
  • To confirm the success of the secure boot fusing operation, you can check the value of the fuse_status U-boot environment variable. This can be checked in Torizon OS using sudo fw_printenv fuse_status.
    • A fuse_status value of "success" means everything went good, "failed" means something was detected to have went wrong. Any other value is unexpected, and should probably be treated as a failure as well.

To be safe please make sure the device is powered during the entirety of this operation until it has fully booted. If your device were to not boot after this operation this could indicate that secure boot is taking effect and preventing an improperly signed bootloader from booting. This is unlikely to happen given the safeguards put in place before the process closes the device. But, it is prudent to double-check the state of your devices after fusing to avoid shipping a non-booting device.

In-Field Programming

This section covers the scenario where you already have devices deployed in the field and want to update them such that they are fused for secure boot. This process will use Torizon Cloud in order to perform the necessary updates on these devices, meaning they should be provisioned for updates. The following will assume that the devices in the field have no prior secure boot configuration on them. To summarize, you will need to perform a series of three updates in order have your device configured with the secure boot fuses set. It is recommended to perform these updates one at a time, so that if one update were to fail proper follow-up action can be planned before proceeding. Last note, if your device already satisfies the conditions for one of the updates you may skip performing that update.

OS Update

First you should update the OS on your devices to at least version 7.2.0 of Torizon OS. Versions before this do not have the support for the logic that will perform the automatic fusing. The OS package used for the update can be customized as long as it’s based on at least the 7.2.0 release from Toradex. Also, ensure the OS is configured for BCoT as well.

You will know whether the OS on your device has the proper support, if you check your device’s /usr/lib/sota/secondaries.json file and see if there is an entry for <module name>-fuses:

...
{
"partial_verifying": false,
"ecu_hardware_id": "verdin-imx8mp-fuses",
"full_client_dir": "/var/sota/storage/fuse",
"ecu_private_key": "sec.private",
"ecu_public_key": "sec.public",
"firmware_path": "/var/sota/storage/fuse/fuse.yml",
"target_name_path": "/var/sota/storage/fuse/target_name",
"metadata_path": "/var/sota/storage/fuse/metadata",
"action_handler_path": "/usr/bin/fuse_actions.sh"
}
...

Furthermore, check your device’s /boot/loader/uEnv.txt file to see if fuse_num exists:

# grep ^fuse_num= /boot/loader/uEnv.txt
fuse_num=1 2 3 4 5 6 7 8

Ensure both of these details are present before proceeding. If either detail is missing, then the version of Torizon OS you are running does not have the proper support to perform the in-field programming.

Bootloader Update

If your devices do not already have a signed bootloader, then you should update the bootloader on your devices. Please make sure to update to a signed bootloader that matches the fuse information that you will use in the next section.

Fuse Update

Before proceeding, make sure your OS and bootloader are sufficiently updated as described in the prior two sections. You will also need your yaml file describing the information that will be fused to your devices as described in "Fuse Yaml File". Before this update is performed an update package must be created and pushed to Torizon Cloud first.

To create this type of update package you can use TorizonCore Builder like so:

$ torizoncore-builder platform push <path to your fuse yaml file> --hardwareid <module name>-fuses --credentials <path to your credentials.zip file>

The only thing to note about this command is the value passed to the --hardwareid flag. This must be of the form <module name>-fuses. For example if this is for an Apalis i.MX8 then it would be "apalis-imx8-fuses", for Verdin i.MX8MP it would be "verdin-imx8mp-fuses". Valid values here are:

  • verdin-imx8mp-fuses
  • verdin-imx8mm-fuses
  • apalis-imx8-fuses
  • apalis-imx6-fuses
  • colibri-imx8x-fuses
  • colibri-imx7-emmc-fuses
  • colibri-imx6-fuses
  • colibri-imx6ull-emmc-fuses

Optionally you can add the --package-name and --package-version flags to this command. This way you can give the update package a particular name and version.

If this command is successful you should see this new package available on your Torizon Cloud account. The command will also produce a canonicalized version of your input yaml file. This is what actually gets pushed to Torizon Cloud, this is done for package consistency. This canonicalized version of your yaml file will be made available by this command on your host machine, having a file extension of either .lock.yml or .lock.yaml.

Once you have pushed your fuse update package to Torizon Cloud, you should now be okay to perform an update with this package. Initiate an update for the <module name>-fuses component and select the package you just pushed.

When the update commences your device will reboot a couple of times in order to program the fuses in the hardware. The result of the update will then be sent to Torizon Cloud for you to review. If the update was successful it can be assumed all 8/16 fuses were programmed and the closing fuse was set (if specified in your yaml file). If the update was a failure it could mean multiple things, either none of the fuses were able to be programmed, or only some of them were successful. In case of failure the device should be inspected to determine the exact state it’s in.

Troubleshooting Tips

  • Both factory and in-field programming use the same core logic for the automated fusing. The results of this logic can be checked via the fuse_status U-Boot environment variable.
    • For in-field programming the update logic uses this variable and clears it, so it’s better to check the update logs in this case.
  • You can always check the value of fuses yourself with the fuse read command in U-Boot. You will need to know what the secure boot fuse locations are for your SoC to use this command properly. Using this command you can make sure the correct values were programmed to the correct fuses.
  • For in-field programming it is possible to configure additional logs during the update process. To do so you need to modify the systemd service that launches the update client (aktualizr.service). When editing the service the following environment variable can be set to enable additional logging for secure boot fuse updates:
[Service]
...
Environment="FUSE_LOG_ENABLED=1"
...
  • This will result in an additional log file being created at /var/lib/rollback-manager/fuse-update.log. Additional information will get written to this file whenever an action occurs related to the secure boot fuse component.
  • It is not recommended to have this logging enabled all the time since it will contribute to flash wear over longer periods of time.
Send Feedback!