Jump to content

Setting up your signing server

From RidgeRun Developer Wiki

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

Follow us on: YouTube Twitter LinkedIn Email Share this page

Share This Page

NVIDIA partner logo NXP partner logo





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-tegra branch.
  • Install helper scripts such as tegra-flash-helper, tegra-signimage-helper and nvflashxmlparse.
  • 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 BUPGENSPECS nor SIGNFILES is present.
  • BUP multi-signing when BUPGENSPECS is present.
  • Direct file signature generation when SIGNFILES is 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.




Cookies help us deliver our services. By using our services, you agree to our use of cookies.