Skip to main content
Version: BSP 7.x.y

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.

info

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.
info

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

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

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
info

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

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

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:

info

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.

info

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.

danger

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"

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.

info

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}"

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
warning

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.

Send Feedback!