Secure Boot Image Signing for TI K3 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 on TI K3-based SoMs.
HSM-backed signing is supported for the complete Secure Boot chain on TI K3-based SoMs. For NXP i.MX-based SoMs, refer to Secure Boot Image Signing for NXP iMX With HSM-Backed Keys.
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 Enablingmeta-toradex-securitylayer and the Secure Boot sections available in Introduction tometa-toradex-security.
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.
Generate the SMPK
The SMPK (Secondary Manufacture Public Key)—stored with the object label custMpk—is forwarded to binman during the Yocto build to sign the K3 boot container. The SoC ROM code verifies this signature at boot.
Initialize a SoftHSM2 token for the SMPK and generate an RSA 2048-bit key pair inside it:
$ softhsm2-util --init-token --free --label "boot-hsm" --so-pin 0000 --pin $USR_PIN
$ softhsm2-util --show-slots
$ pkcs11-tool --module $PKCS11_MODULE_PATH --token-label "boot-hsm" --login --pin $USR_PIN \
--keypairgen --key-type rsa:2048 --id 01 --label "custMpk"
Generate an RSA 2048-bit key pair for the SMPK directly inside the YubiKey. The key is stored at PKCS#11 object ID 0x05.
YubiKey provides several slots that can hold key material. This guide uses object ID 0x05 for the SMPK and 0x06 for the FIT key. You may use different IDs. Keep a note of the ID you choose, as it must match the values configured later in local.conf.
$ export BOOT_KEY_ID=05
$ pkcs11-tool --module $PKCS11_MODULE_PATH --login --login-type so \
--so-pin $PIV_MGMT_KEY_HEX --keypairgen --key-type rsa:2048 --id $BOOT_KEY_ID
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: boot-hsm holds the SMPK (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 SMPK 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 0x06, alongside the SMPK generated in the previous step.
$ export FIT_KEY_ID=06
$ 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:
Add the following variables to local.conf:
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_K3_BINMAN_KEY_URL = "pkcs11:token=boot-hsm;object=custMpk;type=private"
TDX_SIGNED_HSM_K3_OPENSSL_CONF = ""
TDX_SIGNED_HSM_FIT_TOKEN_URL = "token=fit-hsm"
TDX_SIGNED_HSM_FIT_TOKEN_LABEL = "fit-sign-key"
TDX_SIGNED_HSM_K3_BINMAN_KEY_URL is the PKCS#11 URI of the SMPK private key, forwarded to binman to sign the boot container.
TDX_SIGNED_HSM_K3_OPENSSL_CONF is the path to an OpenSSL configuration file that activates the PKCS#11 provider used when signing the K3 boot container — leave it empty to have one generated automatically at build time.
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 object IDs (%05, %06) match the PKCS#11 IDs assigned when generating the keys in the previous steps. The token attribute 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.
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_K3_BINMAN_KEY_URL = "pkcs11:token=YubiKey%20PIV%20%23<YUBIKEY-SERIAL>;id=%05;type=private"
TDX_SIGNED_HSM_K3_OPENSSL_CONF = ""
TDX_SIGNED_HSM_FIT_TOKEN_URL = "token=YubiKey%20PIV%20%23<YUBIKEY-SERIAL>;id=%06"
TDX_SIGNED_HSM_FIT_TOKEN_LABEL = "yk-dev-key"
TDX_SIGNED_HSM_K3_BINMAN_KEY_URL is the PKCS#11 URI of the SMPK private key, forwarded to binman to sign the boot container.
TDX_SIGNED_HSM_K3_OPENSSL_CONF is the path to an OpenSSL configuration file that activates the PKCS#11 provider used when signing the K3 boot container — leave it empty to have one generated automatically at build time.
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.
Program the SMPK Hash Into the SoC
Building a signed image does not, by itself, enforce Secure Boot. The device continues to boot unsigned images until the SMPK hash is programmed into the SoC eFuses. Once the hash is fused, the boot ROM verifies the boot container against it and refuses to boot anything signed with a different key.
The SMPK is the key generated earlier inside the HSM with the object label custMpk. Refer to the Secure Boot documentation for TI K3 SoMs for the procedure to fuse the SMPK hash into the SoC.
Programming keys into the SoC eFuses is an irreversible operation. Once the SMPK hash is fused, the device only boots software signed with that key. A mistake can permanently brick the device. Always verify the full boot chain on a test device before programming production hardware.