Secure Boot Image Signing for NXP iMX With HSM-Backed Keys
Introduction
By default, meta-toradex-security signs Secure Boot artifacts using private keys stored on the build host filesystem. With HSM-backed signing, private keys never leave the token — signing operations are performed inside the HSM through the standard PKCS#11 interface. This article covers how to generate the signing keys inside the HSM and configure the Yocto build system to use them when signing Secure Boot images.
HSM-backed signing is supported for the complete Secure Boot chain on NXP i.MX-based SoMs. Support for TI K3-based SoMs is currently under development.
This article covers two PKCS#11 providers:
- SoftHSM2 — a software implementation of a PKCS#11 token well-suited for development, testing, and CI environments. No dedicated hardware is required.
- YubiKey — a hardware security key used here as an example of a physical PKCS#11 token. Physical tokens store private keys in tamper-resistant hardware, which makes this class of device well suited for production signing workflows where key custody is critical.
Note that the principles covered in this article apply to any PKCS#11-compatible hardware token.
The overall signing workflow is the same for both providers. This article uses tabs to highlight the steps that differ between SoftHSM2 and YubiKey.
Prerequisites
- A Yocto build environment set up for Toradex Linux BSP. Refer to Build a Reference Image.
meta-toradex-securityadded to your build with Secure Boot enabled. Refer to Enabling meta-toradex-security layer and the Secure Boot sections available in Introduction tometa-toradex-security.- Download the NXP Code Signing Tool (CST). Please note you will need an NXP account to access the link for download.
Install the host dependencies for your PKCS#11 provider. The commands below are for Debian/Ubuntu-based distributions and may need to be adapted for other Linux distributions:
$ sudo apt update && sudo apt install -y softhsm2 opensc p11-kit gnutls-bin libp11-3 openssl libengine-pkcs11-openssl
Add your user to the softhsm group to access the token without root privileges:
$ sudo usermod -aG softhsm "$USER"
$ newgrp softhsm
$ sudo apt update && sudo apt install -y opensc p11-kit gnutls-bin libp11-3 openssl yubikey-manager yubico-piv-tool ykcs11 pcscd
Verify that the YubiKey is detected:
$ lsusb | grep Yubi
$ ykman info
Set Up the Environment
Export the following variables, replacing <OE_BUILD_FULL_PATH> with the full path to your OpenEmbedded build directory:
$ export BUILD_DIR=<OE_BUILD_FULL_PATH>
$ export KEYS_DIR=$BUILD_DIR/keys
$ mkdir -p $KEYS_DIR && cd $KEYS_DIR
In this guide, the token PIN is stored in a shell variable for convenience. In a production environment, access to the token should follow proper authentication and authorization practices — for example, requiring the PIN to be entered interactively, injected through a secret management system, and protected by access control and auditing policies appropriate to your security requirements.
Configure the PKCS#11 module path and token PIN:
$ export PKCS11_MODULE_PATH=/usr/lib/softhsm/libsofthsm2.so
$ export PKCS11_PROVIDER_MODULE=$PKCS11_MODULE_PATH
$ export USR_PIN=1234
Create the SoftHSM2 configuration and token directories:
$ export SOFTHSM2_DIR=$KEYS_DIR/softhsm
$ export SOFTHSM2_CONF_DIR=$SOFTHSM2_DIR/conf
$ export SOFTHSM2_TOKENS_DIR=$SOFTHSM2_DIR/tokens
$ export SOFTHSM2_CONF=$SOFTHSM2_CONF_DIR/softhsm2.conf
$ mkdir -p $SOFTHSM2_CONF_DIR $SOFTHSM2_TOKENS_DIR
$ cat >> $SOFTHSM2_CONF << EOF
directories.tokendir = $SOFTHSM2_TOKENS_DIR
objectstore.backend = file
log.level = ERROR
slots.removable = false
slots.mechanisms = ALL
EOF
The following commands will erase all keys and certificates stored on the YubiKey PIV application. Make sure the device does not hold any material you need to keep.
Reset the YubiKey PIV application to start from a clean state:
$ ykman piv reset -f
Configure the PKCS#11 module path and credentials:
$ export PKCS11_MODULE_PATH=/usr/lib/x86_64-linux-gnu/libykcs11.so
$ export PIV_MGMT_KEY_HEX=010203040506070801020304050607080102030405060708
$ export USR_PIN=123456
PIV_MGMT_KEY_HEX and USR_PIN are the YubiKey factory defaults, restored to these values by ykman piv reset -f. They are the same for every device. Before using the YubiKey for real signing, replace both with strong, unique values using ykman piv access change-management-key and ykman piv access change-pin.
Download and Set Up NXP CST
Unpack the CST archive and set the CST_DIR variable:
$ cd $KEYS_DIR
$ tar xfv <path-to-cst-archive>.tgz
$ mv cst-* cst
$ export CST_DIR=$KEYS_DIR/cst
The CST PKI tree scripts do not require changes for SoftHSM2, but they rely on pkcs11-tool being called with the correct token label. Create a wrapper that passes it automatically:
$ mkdir -p $KEYS_DIR/scripts && cd $KEYS_DIR/scripts
$ cat >> pkcs11-tool.sh << 'EOF'
#/bin/sh
pkcs11-tool() {
command pkcs11-tool --token-label "cst-hsm" "$@"
}
export -f pkcs11-tool
EOF
$ source pkcs11-tool.sh
The CST PKI tree scripts must be patched to generate keys inside the YubiKey instead of on the filesystem. The patches make the following adaptations to each script:
- Change the starting PKCS#11 object ID from
1000to0x05, to avoid conflicts with reserved YubiKey PIV slot numbers. - Add Security Officer authentication (
--login-type so --so-pin $PIV_MGMT_KEY_HEX) to allpkcs11-toolcommands that write key objects, as required by YubiKey. - Switch OpenSSL key references from label-based to ID-based format, as required when using ykcs11 as the PKCS#11 provider.
The patches are applicable for CST version 4.0.1. If you are using a different version of CST, the patches may not apply cleanly and may need to be rebased.
Download and apply the patches:
$ cd $CST_DIR/keys
$ curl -O https://docs.toradex.com/118704-hsm_hab4_pki_tree.patch
$ curl -O https://docs.toradex.com/118705-hsm_ahab_pki_tree.patch
$ patch < 118704-hsm_hab4_pki_tree.patch
$ patch < 118705-hsm_ahab_pki_tree.patch
Generate CST Signing Keys
The CST PKI tree scripts generate the key hierarchy used to sign the bootloader image. Run the script that matches your SoM's boot authentication method.
Initialize a SoftHSM2 token for the CST keys:
$ softhsm2-util --init-token --free --label "cst-hsm" --so-pin 0000 --pin $USR_PIN
$ softhsm2-util --show-slots
Generate the PKI tree for your SoM:
The parameters used in the commands below are provided as an example. Make sure to select the parameters appropriate for your use case, including the signature algorithm (-use-ecc, -da), key size (-kl), certificate validity (-duration), number of SRKs (-num-srk), and CA configuration (-srk-ca). Refer to the CST documentation (inside the downloaded CST archive) for a full description of each parameter.
HAB-Based SoMs
$ cd $CST_DIR/keys
$ ./hsm_hab4_pki_tree.sh -existing-ca n -use-ecc n -kl 2048 -duration 100 -num-srk 4 -srk-ca y
AHAB-Based SoMs
$ cd $CST_DIR/keys
$ ./hsm_ahab_pki_tree.sh -existing-ca n -use-ecc n -kl 2048 -da sha256 -duration 100 -srk-ca y
The patched PKI tree scripts generate the keys directly inside the YubiKey. Run the script for your SoM:
The parameters used in the commands below are provided as an example. Make sure to select the parameters appropriate for your use case, including the signature algorithm (-use-ecc, -da), key size (-kl), certificate validity (-duration), number of SRKs (-num-srk), and CA configuration (-srk-ca). Refer to the CST documentation (inside the downloaded CST archive) for a full description of each parameter.
HAB-Based SoMs
$ cd $CST_DIR/keys
$ ./hsm_hab4_pki_tree.sh -existing-ca n -use-ecc n -kl 2048 -duration 100 -num-srk 4 -srk-ca y
AHAB-Based SoMs
$ cd $CST_DIR/keys
$ ./hsm_ahab_pki_tree.sh -existing-ca n -use-ecc n -kl 2048 -da sha256 -duration 100 -srk-ca y
Generate the SRK Fuse Table
The SRK (Super Root Key) fuse table is derived from the public certificates generated in the previous step. The fuse values it contains are later programmed into the SoC to lock the device to your signing keys.
The certificate filenames passed to srktool (for example, SRK1_sha256_2048_65537_v3_ca_crt.pem) reflect the algorithm and key size used when generating the PKI tree. If you used different parameters — such as a different key size or signature algorithm — the filenames will differ, and you must update the -c argument accordingly.
HAB-Based SoMs
$ cd $CST_DIR/crts
$ ../linux64/bin/srktool -h 4 -t SRK_1_2_3_4_table.bin -e SRK_1_2_3_4_fuse.bin -d sha256 -f 1 \
-c ./SRK1_sha256_2048_65537_v3_ca_crt.pem,./SRK2_sha256_2048_65537_v3_ca_crt.pem,./SRK3_sha256_2048_65537_v3_ca_crt.pem,./SRK4_sha256_2048_65537_v3_ca_crt.pem
AHAB-Based SoMs
$ cd $CST_DIR/crts
$ ../linux64/bin/srktool -a -s sha256 -t SRK_1_2_3_4_table.bin -e SRK_1_2_3_4_fuse.bin -f 1 \
-c ./SRK1_sha256_2048_65537_v3_ca_crt.pem,./SRK2_sha256_2048_65537_v3_ca_crt.pem,./SRK3_sha256_2048_65537_v3_ca_crt.pem,./SRK4_sha256_2048_65537_v3_ca_crt.pem
AHABv2-Based SoMs (i.MX9)
$ cd $CST_DIR/crts
$ ../linux64/bin/srktool -a 2 -s sha256 -t SRK_1_2_3_4_table.bin -e SRK_1_2_3_4_fuse.bin -f 1 \
-c ./SRK1_sha256_2048_65537_v3_ca_crt.pem,./SRK2_sha256_2048_65537_v3_ca_crt.pem,./SRK3_sha256_2048_65537_v3_ca_crt.pem,./SRK4_sha256_2048_65537_v3_ca_crt.pem
The srktool output contains the fuse values to program into the SoC. Refer to the Secure Boot documentation for NXP i.MX SoMs for the fuse programming procedure.
Programming fuse values into the SoC is an irreversible operation. Once the SRK fuses are blown, the device will only boot software signed with those keys. A mistake in the fuse values can permanently brick the device. Always verify the full boot chain on a test device before programming production hardware.
Generate the FIT Signing Key
The FIT image signing key is used by mkimage during the Yocto build to sign the FIT image containing the kernel, device trees, and ramdisk.
SoftHSM2 uses two separate tokens: cst-hsm holds the CST PKI tree keys (bootloader signing), and fit-hsm holds the FIT image signing key (kernel signing). Keeping the tokens separate allows you to grant different access levels to each token — for example, tighter control over the bootloader signing keys in production environments.
Initialize the fit-hsm token and generate an RSA 2048-bit key pair inside it:
$ softhsm2-util --init-token --free --label "fit-hsm" --so-pin 0000 --pin $USR_PIN
$ softhsm2-util --show-slots
$ pkcs11-tool --module $PKCS11_MODULE_PATH --token-label "fit-hsm" --login --pin $USR_PIN \
--keypairgen --key-type rsa:2048 --id 01 --label "fit-sign-key"
Generate an RSA 2048-bit key pair for FIT image signing directly inside the YubiKey. The key is stored at PKCS#11 object ID 0x12. YubiKey provides several slots that can hold key material; in this guide, we use 0x12, but you can choose any available ID that does not conflict with the CST certificate objects (which occupy IDs 0x05 through 0x11 for a 4-SRK tree). Keep a note of the ID you choose — you will need it in the local.conf configuration.
$ export FIT_KEY_ID=12 # hex value: 0x12 = 18 decimal
$ pkcs11-tool --module $PKCS11_MODULE_PATH --login --login-type so \
--so-pin $PIV_MGMT_KEY_HEX --keypairgen --key-type rsa:2048 --id $FIT_KEY_ID
Configure the Yocto Build
Configure the build system to use the HSM-backed keys following the steps below:
Run the following command to get the token URL for the FIT image:
$ FIT_TOKEN_URL="$(p11tool --provider "$PKCS11_MODULE_PATH" --list-tokens | \
awk '/^[[:space:]]*URL:[[:space:]]*/ {url=$0; sub(/^[[:space:]]*URL:[[:space:]]*/,"",url)} /^[[:space:]]*Label:[[:space:]]*fit-hsm[[:space:]]*$/ {print url; exit}')" && \
echo "${FIT_TOKEN_URL#pkcs11:}"
The output shall be similar to the following:
model=SoftHSM%20v2;manufacturer=SoftHSM%20project;serial=d65287503fb3e8d6;token=fit-hsm
Add the following variables to local.conf, replacing <FIT-TOKEN-URL> with the token URL extracted from the command output.
The object names in the certificate URIs (for example, SRK1_sha256_2048_65537_v3_ca) reflect the key algorithm and size used when generating the PKI tree. Ensure to update the object names if using different parameters.
HAB-Based SoMs
TDX_SIGNED_HSM = "1"
TDX_SIGNED_HSM_PKCS11_MODULE_PROVIDER = "softhsm-native"
TDX_SIGNED_HSM_PKCS11_MODULE_PATH = "/usr/lib/softhsm/libsofthsm2.so"
TDX_SIGNED_HSM_PKCS11_SOFTHSM_CONF = "${TOPDIR}/keys/softhsm/conf/softhsm2.conf"
TDX_SIGNED_HSM_FIT_TOKEN_URL = "<FIT-TOKEN-URL>"
TDX_SIGNED_HSM_FIT_TOKEN_LABEL = "fit-sign-key"
TDX_IMX_HAB_CST_SRK_CERT = "pkcs11:token=cst-hsm;object=SRK1_sha256_2048_65537_v3_ca;type=cert;pin-value=${TDX_SIGNED_HSM_TOKEN_PIN}"
TDX_IMX_HAB_CST_CSF_CERT = "pkcs11:token=cst-hsm;object=CSF1_1_sha256_2048_65537_v3_usr;type=cert;pin-value=${TDX_SIGNED_HSM_TOKEN_PIN}"
TDX_IMX_HAB_CST_IMG_CERT = "pkcs11:token=cst-hsm;object=IMG1_1_sha256_2048_65537_v3_usr;type=cert;pin-value=${TDX_SIGNED_HSM_TOKEN_PIN}"
AHAB-Based SoMs
TDX_SIGNED_HSM = "1"
TDX_SIGNED_HSM_PKCS11_MODULE_PROVIDER = "softhsm-native"
TDX_SIGNED_HSM_PKCS11_MODULE_PATH = "/usr/lib/softhsm/libsofthsm2.so"
TDX_SIGNED_HSM_PKCS11_SOFTHSM_CONF = "${TOPDIR}/keys/softhsm/conf/softhsm2.conf"
TDX_SIGNED_HSM_FIT_TOKEN_URL = "<FIT-TOKEN-URL>"
TDX_SIGNED_HSM_FIT_TOKEN_LABEL = "fit-sign-key"
TDX_IMX_HAB_CST_SRK_CERT = "pkcs11:token=cst-hsm;object=.%2FSRK1_sha256_2048_65537_v3_ca;type=cert;pin-value=${TDX_SIGNED_HSM_TOKEN_PIN}"
TDX_IMX_HAB_CST_SGK_CERT = "pkcs11:token=cst-hsm;object=.%2FSGK1_1_sha256_2048_65537_v3_usr;type=cert;pin-value=${TDX_SIGNED_HSM_TOKEN_PIN}"
Run the following command to get the YubiKey serial number:
$ ykman info | grep "^Serial number" | cut -d' ' -f 3
The output shall be similar to the following:
22863375
Add the following variables to local.conf, replacing <YUBIKEY-SERIAL> with the YubiKey serial number extracted from the command output.
The certificate object IDs (%06, %07, %08) are assigned by the patched PKI tree scripts during key generation. The model attribute in TDX_SIGNED_HSM_FIT_TOKEN_URL is device-specific and may differ for other YubiKey models. Verify the exact token URL for your device using p11tool --provider $PKCS11_MODULE_PATH --list-tokens.
HAB-Based SoMs
TDX_SIGNED_HSM = "1"
TDX_SIGNED_HSM_PKCS11_MODULE_PROVIDER = "yubico-piv-tool-native"
TDX_SIGNED_HSM_PKCS11_MODULE_PATH = "/usr/lib/libykcs11.so"
TDX_SIGNED_HSM_FIT_TOKEN_URL = "model=YubiKey%20YK5;manufacturer=Yubico%20%28www.yubico.com%29;serial=<YUBIKEY-SERIAL>;token=YubiKey%20PIV%20%23<YUBIKEY-SERIAL>;id=%12"
TDX_SIGNED_HSM_FIT_TOKEN_LABEL = "yk-dev-key"
TDX_IMX_HAB_CST_SRK_CERT = "pkcs11:token=YubiKey%20PIV%20%23<YUBIKEY-SERIAL>;id=%06;type=cert;pin-value=${TDX_SIGNED_HSM_TOKEN_PIN}"
TDX_IMX_HAB_CST_CSF_CERT = "pkcs11:token=YubiKey%20PIV%20%23<YUBIKEY-SERIAL>;id=%07;type=cert;pin-value=${TDX_SIGNED_HSM_TOKEN_PIN}"
TDX_IMX_HAB_CST_IMG_CERT = "pkcs11:token=YubiKey%20PIV%20%23<YUBIKEY-SERIAL>;id=%08;type=cert;pin-value=${TDX_SIGNED_HSM_TOKEN_PIN}"
AHAB-Based SoMs
TDX_SIGNED_HSM = "1"
TDX_SIGNED_HSM_PKCS11_MODULE_PROVIDER = "yubico-piv-tool-native"
TDX_SIGNED_HSM_PKCS11_MODULE_PATH = "/usr/lib/libykcs11.so"
TDX_SIGNED_HSM_FIT_TOKEN_URL = "model=YubiKey%20YK5;manufacturer=Yubico%20%28www.yubico.com%29;serial=<YUBIKEY-SERIAL>;token=YubiKey%20PIV%20%23<YUBIKEY-SERIAL>;id=%12"
TDX_SIGNED_HSM_FIT_TOKEN_LABEL = "yk-dev-key"
TDX_IMX_HAB_CST_SRK_CERT = "pkcs11:token=YubiKey%20PIV%20%23<YUBIKEY-SERIAL>;id=%06;type=cert;pin-value=${TDX_SIGNED_HSM_TOKEN_PIN}"
TDX_IMX_HAB_CST_SGK_CERT = "pkcs11:token=YubiKey%20PIV%20%23<YUBIKEY-SERIAL>;id=%07;type=cert;pin-value=${TDX_SIGNED_HSM_TOKEN_PIN}"
Build the Image
Run the following commands to build the signed image with HSM-backed keys, replacing <pin> with the value of USR_PIN set during the environment setup step.
$ export BB_ENV_PASSTHROUGH_ADDITIONS="$BB_ENV_PASSTHROUGH_ADDITIONS TDX_SIGNED_HSM_TOKEN_PIN"
$ TDX_SIGNED_HSM_TOKEN_PIN=<pin> bitbake tdx-reference-minimal-image
Passing the PIN through the environment reduces its exposure compared to storing it in local.conf, but it does not eliminate all risks. For production environments, consider injecting the PIN through a dedicated secret management mechanism such as a CI secret store or a vault service, with appropriate access control and auditing.