Integrating your signing server with Yocto
🚧 Documentation is under development
The RidgeRun Platform Security Manual guide is currently under active development. Some sections may be incomplete or change without notice.
Questions? Contact RidgeRun or email to support@ridgerun.com.
- Introduction
- General Security Concepts
- Getting Started
- Contact Us
- Sponsor your Favorite Feature
Integrating your signing server with Yocto
Introduction
At this point the signing server should already be installed, configured and validated as described in Setting up your signing server. This page continues from that boundary and focuses only on the Yocto side of the integration.
The goal is simple: Yocto should keep building images as usual, but when an artifact needs a production signature, the build should send it to the signing server instead of reading private keys locally.
To get there, this page adds a small set of custom classes to your own Yocto layer. Those classes override the signing hooks already provided by meta-tegra and replace local signing commands with REST API calls to the signing server.
Before touching Yocto
Before changing Yocto, confirm that:
- The signing server is reachable from the CI runner.
DIGSIGSERVER_KEYFILE_URIis configured on the server.- The server has the required L4T tools under
DIGSIGSERVER_L4T_TOOLS_BASE. - The server has keys for the Yocto
MACHINEname that will be sent in signing requests. - Basic requests to
/sign/tegra/uefiand, if needed,/sign/tegra/ekbsucceed.
Do not configure production private key paths on CI workers. In particular, avoid using these local-signing variables in production CI:
TEGRA_SIGNING_PKCTEGRA_SIGNING_SBKTEGRA_UEFI_DB_KEYTEGRA_UEFI_DB_CERT
Those variables are useful for local development signing, but they put private key material directly into the build environment.
Step 1: Understand where meta-tegra lets you hook in
meta-tegra already has hooks for replacing local signing with custom logic.
For boot firmware and BUP signing, the main hooks are in image_types_tegra.bbclass:
tegraflash_custom_pre: runs after files are staged and before processing.tegraflash_custom_sign_pkg: signs the full tegraflash package.tegraflash_custom_sign_bup: signs/generates BUP payloads.tegraflash_custom_post: runs before the finaltegraflash-taris created.
For UEFI payload signing, meta-tegra uses TEGRA_UEFI_SIGNING_CLASS. The selected class must provide:
tegra_uefi_sbsigntegra_uefi_split_signtegra_uefi_attach_sign
For UEFI capsule signing, meta-tegra uses TEGRA_UEFI_CAPSULE_SIGNING_CLASS. The selected class must provide:
sign_uefi_capsules
Step 2: Define the server settings Yocto will use
Before creating the signing classes, define the small set of non-secret values that Yocto needs in order to call the server:
| Variable | Meaning |
|---|---|
TEGRA_SIGNING_SERVER_URL |
Base URL for the signing server REST API. |
TEGRA_SIGNING_SERVER_MACHINE |
Machine name used by the server to select the key directory. |
TEGRA_SIGNING_SERVER_BSPVERSION |
L4T/BSP version available on the signing server. |
TEGRA_SIGNING_SERVER_SOCTYPE |
Tegra SoC family sent to the signing endpoint, such as tegra234.
|
In CI, export the same information as environment variables:
export TEGRA_SIGNING_SERVER_URL="https://signing.example.com" export TEGRA_SIGNING_SERVER_MACHINE="jetson-agx-orin-devkit" export TEGRA_SIGNING_SERVER_BSPVERSION="36.4.3" export TEGRA_SIGNING_SERVER_SOCTYPE="tegra234"
Step 3: Route tegraflash package signing to the server
By default tegraflash_custom_sign_pkg runs local tegra-flash-helper.sh when TEGRA_SIGNING_ARGS is set or TEGRA_SIGNING_ALWAYS = "1". For server-side signing, override the function and call /sign/tegra.
Create a class in your own layer. This class packages the staged tegraflash files, sends them to /sign/tegra, and unpacks the signed response back into the build work directory:
# meta-myproduct/classes/tegra-remote-signing.bbclass
TEGRA_SIGNING_SERVER_URL ?= "http://signing-server.example.com:9999"
TEGRA_SIGNING_SERVER_MACHINE ?= "${TNSPEC_MACHINE}"
TEGRA_SIGNING_SERVER_BSPVERSION ?= "${L4T_VERSION}"
TEGRA_SIGNING_SERVER_SOCTYPE ?= "${SOC_FAMILY}"
TEGRA_SIGNING_SERVER_DTBFILE ?= "${@os.path.basename(d.getVar('KERNEL_DEVICETREE').split()[0]) if d.getVar('KERNEL_DEVICETREE') else ''}"
tegraflash_remote_manifest() {
cat > MANIFEST <<EOF
DTBFILE=${TEGRA_SIGNING_SERVER_DTBFILE}
LNXFILE=${LNXFILE}
ODMDATA=${TEGRA_FLASHVAR_ODMDATA}
EMMC_BCTS=${EMC_BCTS}
${@'\n'.join(d.getVar("TEGRA_SIGNING_ENV").split())}
EOF
}
tegraflash_custom_sign_pkg() {
tegraflash_remote_manifest
tar -czf unsigned-tegraflash.tar.gz ./*
curl --fail --connect-timeout 30 --max-time 1800 --retry 2 \
-X POST \
-F "machine=${TEGRA_SIGNING_SERVER_MACHINE}" \
-F "soctype=${TEGRA_SIGNING_SERVER_SOCTYPE}" \
-F "bspversion=${TEGRA_SIGNING_SERVER_BSPVERSION}" \
-F "artifact=@unsigned-tegraflash.tar.gz;type=application/octet-stream" \
--output signed-tegraflash.tar.gz \
"${TEGRA_SIGNING_SERVER_URL}/sign/tegra"
tar -xzf signed-tegraflash.tar.gz
tegraflash_post_sign_pkg
}
tegraflash_custom_sign_bup() {
tegraflash_remote_manifest
echo 'BUPGEN=1' >> MANIFEST
echo 'BUPGENSPECS=${TEGRA_BUPGEN_SPECS}' >> MANIFEST
tar -czf unsigned-bup.tar.gz ./*
curl --fail --connect-timeout 30 --max-time 1800 --retry 2 \
-X POST \
-F "machine=${TEGRA_SIGNING_SERVER_MACHINE}" \
-F "soctype=${TEGRA_SIGNING_SERVER_SOCTYPE}" \
-F "bspversion=${TEGRA_SIGNING_SERVER_BSPVERSION}" \
-F "artifact=@unsigned-bup.tar.gz;type=application/octet-stream" \
--output signed-bup.tar.gz \
"${TEGRA_SIGNING_SERVER_URL}/sign/tegra"
tar -xzf signed-bup.tar.gz
}
Enable the class from your distro or machine configuration:
INHERIT += "tegra-remote-signing" TEGRA_SIGNING_ALWAYS = "1"
TEGRA_SIGNING_ALWAYS = "1" forces the signing hook to run even though TEGRA_SIGNING_ARGS is intentionally empty.
Step 4: Route UEFI payload signing to the server
The default tegra-uefi-signing class signs locally with sbsign and openssl when TEGRA_UEFI_DB_KEY and TEGRA_UEFI_DB_CERT are set. To use the signing server, provide your own class and point TEGRA_UEFI_SIGNING_CLASS to it.
Create a signing class that sends UEFI payloads to /sign/tegra/uefi:
# meta-myproduct/classes/tegra-remote-uefi-signing.bbclass
TEGRA_SIGNING_SERVER_URL ?= "http://signing-server.example.com:9999"
TEGRA_SIGNING_SERVER_MACHINE ?= "${MACHINE}"
TEGRA_UEFI_USE_SIGNED_FILES ?= "true"
TEGRA_UEFI_SIGNING_TASKDEPS ?= "curl-native:do_populate_sysroot"
TEGRA_UEFI_SIGNING_DEPENDS ?= "curl-native"
TEGRA_UEFI_SIGNING_FILECHECKSUMS ?= ""
tegra_uefi_remote_sign() {
local signing_type="$1"
local input="$2"
local output="$3"
curl --fail --connect-timeout 30 --max-time 600 --retry 2 \
-X POST \
-F "machine=${TEGRA_SIGNING_SERVER_MACHINE}" \
-F "signing_type=${signing_type}" \
-F "artifact=@${input};type=application/octet-stream" \
--output "${output}" \
"${TEGRA_SIGNING_SERVER_URL}/sign/tegra/uefi"
}
tegra_uefi_sbsign() {
tegra_uefi_remote_sign sbsign "$1" "$1.signed"
mv "$1.signed" "$1"
}
tegra_uefi_split_sign() {
tegra_uefi_remote_sign signature "$1" "$1.sig"
}
tegra_uefi_attach_sign() {
tegra_uefi_remote_sign attach_signature "$1" "$1.signed"
}
Enable it from your distro configuration, machine configuration, or conf/local.conf while testing:
TEGRA_UEFI_SIGNING_CLASS = "tegra-remote-uefi-signing"
This hook is used by the kernel, DTBs, L4TLauncher and cboot image paths in meta-tegra.
Step 5: Route UEFI capsule signing to the server
The default capsule signing class runs GenerateCapsule.py locally with local capsule certificates. Override it to call /sign/tegra/ueficapsule.
Create a capsule signing class that sends generated BUP payloads to /sign/tegra/ueficapsule:
# meta-myproduct/classes/tegra-remote-uefi-capsule-signing.bbclass
inherit l4t_bsp
TEGRA_SIGNING_SERVER_URL ?= "http://signing-server.example.com:9999"
TEGRA_SIGNING_SERVER_MACHINE ?= "${MACHINE}"
TEGRA_SIGNING_SERVER_BSPVERSION ?= "${L4T_VERSION}"
TEGRA_SIGNING_SERVER_SOCTYPE ?= "${SOC_FAMILY}"
TEGRA_UEFI_CAPSULE_SIGNING_EXTRA_DEPS ?= "curl-native:do_populate_sysroot"
sign_uefi_capsules() {
if [ -e ${B}/${BUPFILENAME}.bl_only.bup-payload ]; then
curl --fail --connect-timeout 30 --max-time 1800 --retry 2 \
-X POST \
-F "machine=${TEGRA_SIGNING_SERVER_MACHINE}" \
-F "soctype=${TEGRA_SIGNING_SERVER_SOCTYPE}" \
-F "bspversion=${TEGRA_SIGNING_SERVER_BSPVERSION}" \
-F "guid=${GUID}" \
-F "artifact=@${B}/${BUPFILENAME}.bl_only.bup-payload;type=application/octet-stream" \
--output ./tegra-bl.cap \
"${TEGRA_SIGNING_SERVER_URL}/sign/tegra/ueficapsule"
fi
if [ -e ${B}/${BUPFILENAME}.kernel_only.bup-payload ]; then
curl --fail --connect-timeout 30 --max-time 1800 --retry 2 \
-X POST \
-F "machine=${TEGRA_SIGNING_SERVER_MACHINE}" \
-F "soctype=${TEGRA_SIGNING_SERVER_SOCTYPE}" \
-F "bspversion=${TEGRA_SIGNING_SERVER_BSPVERSION}" \
-F "guid=${GUID}" \
-F "artifact=@${B}/${BUPFILENAME}.kernel_only.bup-payload;type=application/octet-stream" \
--output ./tegra-kernel.cap \
"${TEGRA_SIGNING_SERVER_URL}/sign/tegra/ueficapsule"
fi
}
Enable it from your distro configuration, machine configuration, or conf/local.conf while testing:
TEGRA_UEFI_CAPSULE_SIGNING_CLASS = "tegra-remote-uefi-capsule-signing"
Step 6: Decide how EKB generation will be handled
The signing server exposes /sign/tegra/ekb for tegra194 and tegra234. However, in most production setups, the EKB does not need to be regenerated on every Yocto build. Treat it as a controlled security artifact unless your product requires per-build, per-machine or per-variant EKB generation.
This matters because meta-tegra documents local build-time EKB generation through variables such as:
TEGRA_EKB_OEM_K1 = "/path/to/oem_k1.key" TEGRA_EKB_SYM2 = "/path/to/sym2.key" TEGRA_EKB_AUTH = "/path/to/auth.key"
For production CI, avoid placing these key files on the runner. Use the approach that matches how often your EKB actually changes:
- If the EKB is stable, generate it inside the signing environment and provide only the resulting
eks.imgto the Yocto build. - If the EKB must be generated automatically per machine, variant or release, add a bbappend around the EKB recipe that calls
/sign/tegra/ekband stages the returnedekb.imgaseks.img.
Manual server-side verification:
curl --fail -X POST \
-F "machine=jetson-agx-orin-devkit" \
-F "soctype=tegra234" \
-F "bspversion=36.4.3" \
--output ekb.img \
http://signing-server.example.com:9999/sign/tegra/ekb
Step 7: Put the Yocto configuration together
After the classes exist in your layer, the final configuration is small:
INHERIT += "tegra-remote-signing"
TEGRA_SIGNING_ALWAYS = "1"
TEGRA_UEFI_SIGNING_CLASS = "tegra-remote-uefi-signing"
TEGRA_UEFI_CAPSULE_SIGNING_CLASS = "tegra-remote-uefi-capsule-signing"
TEGRA_SIGNING_SERVER_URL = "${@os.getenv('TEGRA_SIGNING_SERVER_URL') or 'https://signing.example.com'}"
TEGRA_SIGNING_SERVER_MACHINE = "${MACHINE}"
TEGRA_SIGNING_SERVER_BSPVERSION = "${L4T_VERSION}"
TEGRA_SIGNING_SERVER_SOCTYPE = "${SOC_FAMILY}"
Step 8: Verify from Yocto
Run the smallest target that exercises the hook you are integrating:
bitbake <image-name> -c image_tegraflash_tar -f
For UEFI payload signing, verify that signed outputs appear in tmp/deploy/images/${MACHINE}:
ls -l tmp/deploy/images/${MACHINE}/*.signed
ls -l tmp/deploy/images/${MACHINE}/*.cboot
For capsule signing:
bitbake tegra-uefi-capsules -c compile -f
ls -l tmp/deploy/images/${MACHINE}/tegra-bl.cap