Setting up your signing server
🚧 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
Setting up your signing server
By the end of this page, you should have a signing service running locally or on a controlled host, with the required NVIDIA L4T tools, signing keys, REST endpoints, and basic validation in place. The Yocto integration is intentionally left for the next page.
For NVIDIA Jetson platforms the server must be prepared for several independent signing domains:
- Boot firmware and BUP payload signing with NVIDIA L4T tools.
- UEFI payload signing for the kernel, device trees, EFI applications and attached signatures.
- UEFI capsule signing for firmware update payloads.
- Optional EKB generation for devices that use OP-TEE/NVIDIA key material.
Implementation used in this guide
Fortunately, there is already a working implementation we can use: digsigserver. This project was created by Matt Madison, who is also one of the main contributors and maintainers behind meta-tegra. That makes it a useful reference point for anyone integrating their own signing server because it was designed with embedded Linux and Yocto-based signing use cases in mind.
The sections below configure that implementation and stop at the server boundary. The Yocto-specific bbclasses and meta-tegra hooks are covered separately in Integrating your signing server with Yocto.
What you are going to run
The repository contains digsigserver, a Python/Sanic REST service that receives signing requests under /sign/, runs the correct vendor signing tools in a temporary work directory, and returns the signed artifact.
The service does not manage key lifecycle. It only loads keys from the configured URI when a request is processed, uses them in a temporary directory, and cleans them up after the signing command finishes. Key storage can be a local file:// path or an s3:// URI.
By default, once started, the command-line entrypoint exposes the server on:
http://<signing-server-host>:9999
The server is plain HTTP by default. In production it should be placed behind a network boundary or reverse proxy that provides TLS, authentication, authorization, request logging and rate limiting.
Step 1: Install the server
Install the Python package in a virtual environment or container:
cd digsigserver python3 -m venv .venv . .venv/bin/activate pip install .
Step 2: Configure the server environment
The server is configured with environment variables. At minimum, it needs to know where the signing keys live and where the NVIDIA L4T tools are installed.
| Variable | Required | Default | Description |
|---|---|---|---|
DIGSIGSERVER_KEYFILE_URI |
Yes | none at startup | Base URI for signing keys. The CLI refuses to start if it is not set. Supports file:// and s3://.
|
DIGSIGSERVER_L4T_TOOLS_BASE |
For Jetson signing | /opt/nvidia |
Directory containing L4T tool installs, for example /opt/nvidia/L4T-36.4.3-tegra234/Linux_for_Tegra.
|
DIGSIGSERVER_RESPONSE_TIMEOUT |
Recommended | 600 |
Sanic response timeout in seconds. BUP and capsule signing can take several minutes. |
DIGSIGSERVER_REQUEST_MAX_SIZE |
Recommended | 600000000 |
Maximum upload size. Increase if CI sends large tegraflash/BUP tarballs. |
DIGSIGSERVER_LOG_LEVEL |
Optional | DEBUG |
Server log level. |
DIGSIGSERVER_YUBIHSM_PASSWORD |
Optional | unset | Used by PKCS#11/YubiHSM flows; the server redacts it from logs. |
Step 3: Prepare the L4T tools
The signing server needs the NVIDIA L4T BSP tools because the actual signing operation is still performed with NVIDIA tooling. The useful part is that the digsigserver repository already includes Dockerfiles that download and prepare those tools for several Jetson Linux releases.
Those Dockerfiles do more than download the BSP. They also:
- Download the matching Jetson Linux BSP archive from NVIDIA.
- Download the public sources when needed, including OP-TEE sources used by EKB generation.
- Clone the matching
meta-tegrabranch. - Install helper scripts such as
tegra-flash-helper,tegra-signimage-helperandnvflashxmlparse. - Apply patches required by the signing scripts.
- Stage everything under the layout expected by the server.
Available L4T Dockerfiles in this repository include:
| Dockerfile | Jetson Linux / L4T version | Notes |
|---|---|---|
docker/Dockerfile.l4t-36.4.4 |
R36.4.4 | JetPack 6.x / Orin generation |
docker/Dockerfile.l4t-36.4.3 |
R36.4.3 | JetPack 6.x / Orin generation |
docker/Dockerfile.l4t-36.4.0 |
R36.4.0 | JetPack 6.x / Orin generation |
docker/Dockerfile.l4t-35.6.0 |
R35.6.0 | JetPack 5.x / Xavier and Orin generation |
docker/Dockerfile.l4t-35.5.0 |
R35.5.0 | JetPack 5.x / Xavier and Orin generation |
docker/Dockerfile.l4t-35.4.1 |
R35.4.1 | JetPack 5.x / Xavier and Orin generation |
docker/Dockerfile.l4t-32.7.4 |
R32.7.4 | JetPack 4.x / older Jetson platforms |
docker/Dockerfile.l4t-32.5.2 |
R32.5.2 | JetPack 4.x / older Jetson platforms |
For example, to prepare L4T R36.4.3 tools and then build the final signing-server image:
cd digsigserver
docker build . \
-f docker/Dockerfile.l4t-36.4.3 \
-t l4t-release:36.4.3
docker build . \
-f docker/Dockerfile \
-t digsigserver:latest
If you need more than one L4T version, build each Dockerfile.l4t-* image and make sure docker/Dockerfile copies the corresponding /opt/nvidia content into the final image. The repository's Docker README shows this multi-stage pattern.
After the tools are prepared, /sign/tegra, /sign/tegra/ueficapsule and /sign/tegra/ekb expect them below:
${DIGSIGSERVER_L4T_TOOLS_BASE}/L4T-<bspversion>-<soctype-dir>/Linux_for_Tegra
Examples:
/opt/nvidia/L4T-35.4.1-tegra186/Linux_for_Tegra /opt/nvidia/L4T-36.4.3-tegra234/Linux_for_Tegra
Step 4: Prepare the signing keys
Use one key directory per Yocto MACHINE value:
/srv/signing-keys/
+-- jetson-agx-orin-devkit/
+-- tegrasign/
| +-- rsa_priv.pem
| +-- sbk.txt
| +-- user_key.txt
+-- uefisign/
| +-- db.key
| +-- db.crt
+-- ueficapsulesign/
| +-- signer_private_cert.pem
| +-- other_public_cert.pem
| +-- trusted_public_cert.pem
+-- ekbsign/
+-- oem_k1.key
+-- fixed-vector
+-- uefi-variable-authentication.key
+-- kernel-encryption.key
+-- disk-encryption.key
rsa_priv.pem is mandatory for Tegra boot signing. sbk.txt is required only when SBK fuses are used. user_key.txt is only needed for BSP/platform combinations that encrypt selected boot firmware with a user key.
Step 5: Start the service
Once the tools and keys are in place, export the server configuration:
export DIGSIGSERVER_KEYFILE_URI="file:///srv/signing-keys" export DIGSIGSERVER_L4T_TOOLS_BASE="/opt/nvidia" export DIGSIGSERVER_RESPONSE_TIMEOUT="600" export DIGSIGSERVER_REQUEST_MAX_SIZE="600000000" export DIGSIGSERVER_LOG_LEVEL="INFO"
Then start the service:
digsigserver --address 0.0.0.0 --port 9999
What the REST API exposes
All endpoints are POST requests using multipart form data. The table below focuses on the Jetson Secure Boot endpoints used by this guide.
The examples assume these shell variables:
export SIGNING_SERVER_URL="http://127.0.0.1:9999" export MACHINE="jetson-agx-orin-devkit" export SOCTYPE="tegra234" export BSP_VERSION="36.4.3"
Replace the artifact filenames in the commands with the files generated by your build or test environment.
| Purpose | Endpoint | Keys used by the server | Example request |
|---|---|---|---|
| Tegra bootloader, firmware, kernel/DTB boot package and BUP signing | POST /sign/tegra
|
${DIGSIGSERVER_KEYFILE_URI}/${machine}/tegrasign/rsa_priv.pem, optional sbk.txt, optional user_key.txt
|
curl --fail -X POST \
-F "machine=${MACHINE}" \
-F "soctype=${SOCTYPE}" \
-F "bspversion=${BSP_VERSION}" \
-F "artifact=@unsigned-tegraflash.tar.gz;type=application/octet-stream" \
--output signed-tegraflash.tar.gz \
"${SIGNING_SERVER_URL}/sign/tegra"
|
| UEFI Secure Boot payload signing | POST /sign/tegra/uefi
|
${DIGSIGSERVER_KEYFILE_URI}/${machine}/uefisign/db.key, db.crt
|
curl --fail -X POST \
-F "machine=${MACHINE}" \
-F "signing_type=signature" \
-F "artifact=@unsigned.bin;type=application/octet-stream" \
--output unsigned.bin.sig \
"${SIGNING_SERVER_URL}/sign/tegra/uefi"
|
| UEFI capsule generation/signing | POST /sign/tegra/ueficapsule
|
${DIGSIGSERVER_KEYFILE_URI}/${machine}/ueficapsulesign/signer_private_cert.pem, other_public_cert.pem, trusted_public_cert.pem
|
curl --fail -X POST \
-F "machine=${MACHINE}" \
-F "soctype=${SOCTYPE}" \
-F "bspversion=${BSP_VERSION}" \
-F "guid=bf0d4599-20d4-414e-b2c5-3595b1cda402" \
-F "artifact=@tegra-bl.bup-payload;type=application/octet-stream" \
--output tegra-bl.cap \
"${SIGNING_SERVER_URL}/sign/tegra/ueficapsule"
|
| EKB generation | POST /sign/tegra/ekb
|
${DIGSIGSERVER_KEYFILE_URI}/${machine}/ekbsign/oem_k1.key, fixed-vector, uefi-variable-authentication.key, optional encryption keys
|
curl --fail -X POST \
-F "machine=${MACHINE}" \
-F "soctype=${SOCTYPE}" \
-F "bspversion=${BSP_VERSION}" \
--output ekb.img \
"${SIGNING_SERVER_URL}/sign/tegra/ekb"
|
For Jetson, the Tegra boot signing endpoint accepts tegra186, tegra194, tegra210 and tegra234. The UEFI capsule and EKB endpoints in this server implementation accept tegra194 and tegra234.
The same server also exposes useful endpoints for update artifact signing. They are not part of the Jetson Secure Boot chain itself, but they can be useful if the same production pipeline also signs OTA artifacts:
| Purpose | Endpoint | Keys used by the server | Example request |
|---|---|---|---|
| Mender artifact signing | POST /sign/mender
|
${DIGSIGSERVER_KEYFILE_URI}/${distro}/mender/private.key
|
curl --fail -X POST \
-F "distro=my-distro" \
-F "artifact-uri=s3://my-bucket/path/image.mender" \
"${SIGNING_SERVER_URL}/sign/mender"
|
SWUpdate sw-description signing
|
POST /sign/swupdate
|
${DIGSIGSERVER_KEYFILE_URI}/${distro}/swupdate/rsa-private.key for RSA, or cms.cert and cms-private.key for CMS
|
curl --fail -X POST \
-F "distro=my-distro" \
-F "method=RSA" \
-F "sw-description=@sw-description;type=application/octet-stream" \
--output sw-description.sig \
"${SIGNING_SERVER_URL}/sign/swupdate"
|
Understanding the Tegra signing request
/sign/tegra receives a gzip-compressed tarball. The tarball must contain the boot artifacts plus a MANIFEST file. The server extracts the tarball, reads MANIFEST, and decides which Tegra signer mode to use:
- Normal package signing when neither
BUPGENSPECSnorSIGNFILESis present. - BUP multi-signing when
BUPGENSPECSis present. - Direct file signature generation when
SIGNFILESis present.
The manifest is a simple KEY=value file. Important fields are:
DTBFILE=<dtb file in the tarball> LNXFILE=<boot image file in the tarball> ODMDATA=<platform ODM data> EMMC_BCTS=<optional BCT config list> BUPGEN=1 BUPGENSPECS=fab=300;boardsku=0000;boardrev=;chipsku=00:00:00:D0;bup_type=bl
The exact files in the tarball should match the files staged by meta-tegra in the tegraflash work directory. You do not need to handcraft this request in production; the Yocto-side packaging and request logic is covered in Integrating your signing server with Yocto.
Step 6: Verify the server
At this point, the server should be running and reachable. The goal of this section is not to validate the full Yocto integration yet; it is only to confirm that the service can answer requests and access the keys/tools required by the endpoints you plan to use.
Confirm that the service answers
The server has no health endpoint in this repository, so use a negative signing request to confirm that Sanic is listening:
curl -i -X POST http://127.0.0.1:9999/sign/tegra/uefi
Expected result: an HTTP response from the server, normally 400 Invalid artifact. Connection refused means the service is not listening or the port is blocked.
Test a simple UEFI signing request
Create a small test file and send it to the UEFI endpoint:
printf 'test\n' > unsigned.bin
curl --fail -X POST \
-F "machine=jetson-agx-orin-devkit" \
-F "signing_type=signature" \
-F "artifact=@unsigned.bin;type=application/octet-stream" \
--output unsigned.bin.sig \
http://127.0.0.1:9999/sign/tegra/uefi
test -s unsigned.bin.sig
Expected result: unsigned.bin.sig exists and is non-empty.