Skip to content

Usage

The client-side tooling is the signing-tool command, which gathers the different use cases into subcommands. A companion config-init command generates a configuration file so settings (and the token) don't have to be passed on every command line.

See Setup for installation.

Getting help

The top-level help lists every subcommand:

(venv) $ signing-tool -h
usage: signing-tool [-h] [--version] (-n N | -c) [-t <TOKEN>] [--skipssl]
                    [--json] [-a <ADDRESS>]
                    {imagesigning,group,product,production,escrow,fusemap,secrets,profile,client,ca}
                    ...

Example Signing API Tool

positional arguments:
  {imagesigning,group,product,production,escrow,fusemap,secrets,profile,client,ca}
                        Top level commands
    imagesigning        Imagesigning functionality
    group               Config group management functionality
    product             Product management functionality
    production          Production functionality E.g., Device certificates
    escrow              Escrow management functionality
    fusemap             Fusemap management functionality
    secrets             Secret management functionality
    profile             PKI Profile functionality
    client              Client functionality
    ca                  CA functionality

options:
  -h, --help            show this help message and exit
  --version, -V         show program's version number and exit
  -n N                  Config file path
  -c                    Config from command line options
  -t <TOKEN>            OAUTH2 token for the API authentication. RECOMMENDED:
                        '@/path/to/file' reads the token from a file, or '@-'
                        reads it from stdin - both keep the secret out of
                        shell history and process listings. NOT RECOMMENDED:
                        passing the token literally (it is visible in shell
                        history and process listings).
  --skipssl             Skip TLS verification (INSECURE; local development
                        only)
  --json                Machine-readable output: exactly one JSON document on
                        stdout - the API response, or a summary of what the
                        command did when there is no response object
  -a <ADDRESS>          API address (eg. https://localhost:1234/api/v1)

Each subcommand has its own help (signing-tool <subcmd> -h) and each operation a further level (signing-tool <subcmd> <op> -h). For example:

(venv) $ signing-tool product -h
usage: signing-tool product [-h]
                            {add,approve,get,getapproval,getpubkey,getall} ...

positional arguments:
  {add,approve,get,getapproval,getpubkey,getall}
                        Product subcommands
    add                 Add new product request from a template file
    approve             Approve product
    get                 Get product
    getapproval         Get products requiring approval
    getpubkey           Export one operation's public key (for verifying what
                        it signs)
    getall              Get all products

Providing the token

The token is short-lived and is your credential to the API — treat it like a password. Do not pass it literally on the command line (-t "$TOKEN"); it ends up in your shell history and in process listings (ps, /proc/<pid>/cmdline). If you do, the tool prints a warning.

The recommended approach is a configuration file that references the token by file, so the secret is never stored in the config and never appears on a command line. Generate one with config-init:

# The token lives in its own file, restricted to your user:
(venv) $ printf '%s' "$TOKEN" > jwt.token && chmod 600 jwt.token

# config-init writes a 'token_file' reference (not the token itself) and
# creates the config 0600 on POSIX:
(venv) $ config-init -n test.ini -t @jwt.token -a https://app.laavat.io/<CustomerName>/api/v1

The resulting test.ini:

[service]
url = https://app.laavat.io/<CustomerName>/api/v1
token_file = jwt.token

Then run any command with -n test.ini — no token on the command line:

(venv) $ signing-tool -n test.ini product getall

Other ways to supply the token

  • -t @/path/to/file reads the token directly from a file.
  • -t @- reads it from stdin: printf '%s' "$TOKEN" | signing-tool -c -t @- -a <url> product getall. This suits CI secret managers, which expose secrets as files or on stdin. Interactive commands (escrow add, group add) cannot use -t @-, because they read from stdin themselves — use a config file or -t @/path for those.
  • TLS verification is on by default. --skipssl disables it and is intended for local development only.

The remaining examples on this page use -n test.ini.

Automating from CI

The client is usable from scripts and build systems without scraping its output.

Exit codes

Errors go to stderr and the process exits non-zero, so a script can tell success from failure:

Code Meaning
0 Success
1 Error — API, HTTP, transport, anything unclassified
2 Usage — a missing/malformed option, or a token that can't be resolved
3 Not ready — --wait ran out of time with the request still pending. The request is still alive; approve it and re-run
4 The request is Rejected or Failed — it can never produce a payload, so retrying is pointless
130 Interrupted with Ctrl-C

--json

--json prints exactly one JSON document on stdout (and nothing else), for every command family. Human output is unchanged without it. It may appear before or after the subcommand.

# Capture the request id without scraping prose off stdout:
(venv) $ ID=$(signing-tool --json -n test.ini imagesigning add SignUBoot \
       -N build-42 -D "u-boot" -P "$PRODUCT" --operid "$OPER" \
       -F u-boot.imx | jq -r .id)

JSON keys are snake_case

The document is the SDK's view of the API object, so the API's modifiedSHA256 comes out as modified_sha256, originalSHA256 as original_sha256. A jq expression written against the wire names silently returns null.

Commands whose human-readable output is a single sentence (client approve, ca patch, ca getcsr, ca getcrl, secrets get, secrets getkeys, product getpubkey) emit a document describing what they did under --json. The interactive commands (escrow add, group add, and the escrow/group/ product approvals) have no machine-readable form and refuse --json.

Signing

The signing flow is: submit a request, wait for approval if the product's approval rule requires it, then download the signed payload with imagesigning get.

CST signing (NXP i.MX6, HAB/AHAB)

NXP i.MX6 packages can be signed with the CST-tool-based SignHAB operation.

# Submit
(venv) $ signing-tool -n test.ini imagesigning add SignHAB \
    -N test -D test2 -P adcb30d8-f009-438e-b1b2-96f507b306cb \
    --operid 4add90e9-ffb3-4708-9554-ed2e82e8fd71 -F SPL-hab

# Approve
(venv) $ signing-tool -n approver.ini imagesigning approve \
    -I a83081a6-1d3b-4117-a81b-0ebcfcf0669c

# Get the signed payload
(venv) $ signing-tool -n test.ini imagesigning get \
    -I a83081a6-1d3b-4117-a81b-0ebcfcf0669c -O /tmp/signed.bin

SPSDK signing (AHAB)

SignSPSDK signs an NXP AHAB boot container (i.MX8QXP / i.MX9) with the SPSDK tool. Reach for SignSPSDK for SPSDK-based AHAB, and SignHAB for the CST-tool HAB/AHAB flow.

The payload is a single compressed archive containing the image, an SPSDK configuration file, and a request.json naming them. See SPSDK signing for the archive contents and which configuration fields you own.

# Build the archive (image + SPSDK config + request.json)
$ tar czf payload.tar.gz request.json mimx9352-sign.yaml imx9-container.bin

# Submit. SRK0 signs by default; --signingKeyIndex 0-3 selects another SRK.
(venv) $ signing-tool -n test.ini imagesigning add SignSPSDK \
    -N build-42 -D "imx93 AHAB" \
    -P adcb30d8-f009-438e-b1b2-96f507b306cb \
    --operid 4add90e9-ffb3-4708-9554-ed2e82e8fd71 \
    --signingKeyIndex 0 -F payload.tar.gz

# Approve
(venv) $ signing-tool -n approver.ini imagesigning approve \
    -I a83081a6-1d3b-4117-a81b-0ebcfcf0669c

# Get the signed payload
(venv) $ signing-tool -n test.ini imagesigning get \
    -I a83081a6-1d3b-4117-a81b-0ebcfcf0669c -O /tmp/signed.bin

FIT signing

# Submit
(venv) $ signing-tool -n test.ini imagesigning add SignUBoot \
    -N test -D test2 -P adcb30d8-f009-438e-b1b2-96f507b306cb \
    --operid 4add90e9-ffb3-4708-9554-ed2e82e8fd71 -F linux-uImage.bin

# Approve
(venv) $ signing-tool -n approver.ini imagesigning approve \
    -I a83081a6-1d3b-4117-a81b-0ebcfcf0669c

# Get the signed payload
(venv) $ signing-tool -n test.ini imagesigning get \
    -I a83081a6-1d3b-4117-a81b-0ebcfcf0669c -O /tmp/signed.bin

RAUC signing

# Submit
(venv) $ signing-tool -n test.ini imagesigning add RaucBundleSigningWithOneTimeKey \
    -N test -D test2 -P adcb30d8-f009-438e-b1b2-96f507b306cb \
    --operid 4add90e9-ffb3-4708-9554-ed2e82e8fd71 -F rauc-verity.tar.gz

# Approve
(venv) $ signing-tool -n approver.ini imagesigning approve \
    -I a83081a6-1d3b-4117-a81b-0ebcfcf0669c

# Get the signed payload
(venv) $ signing-tool -n test.ini imagesigning get \
    -I a83081a6-1d3b-4117-a81b-0ebcfcf0669c -O /tmp/signed.rauc.bundle

--metadata

Every imagesigning add operation accepts --metadata '[{"name":"...","value":"..."}]' to attach JSON key/value pairs to the request.

Downloading: waiting and verification

--wait

Instead of hand-rolling a retry loop, imagesigning get --wait polls until the request is Ready and then downloads. It requires -I.

(venv) $ signing-tool -n test.ini imagesigning get -I "$ID" --wait \
    --wait-timeout 1800 -O u-boot.signed

--wait-timeout SEC (default 1800) bounds the wait. It is not a hard guarantee: one check is always made at the boundary with a five-second grace, so the command can return up to ~5s late rather than stop short and miss an approval made in the last stretch. A caller that needs a hard bound must impose it.

On timeout with the request still pending, get --wait exits 3 — the request is still alive, so approve it and re-run. It exits 4 if the request was rejected or failed. Without --wait, get -O on a request that isn't Ready fails the same way rather than silently writing nothing.

The two digests

get answers two different questions with two different digests:

Check Answers Default
modifiedSHA256 (automatic) Did the artifact arrive intact? Always on — refuses to write the file on a mismatch, or when the service reports no digest
originalSHA256 (via --expect-input FILE) Was it signed over the bytes I still hold? Off — you name the file

The first can't answer the second: approval is a human step that can take days, over which the build tree moves (a rebuild, another branch, a stale request id), so a perfectly intact artifact can carry a signature over an input that's no longer on disk. --expect-input compares the service's originalSHA256 against your file before downloading, and requires -I:

(venv) $ signing-tool -n test.ini imagesigning get -I "$RID" --wait \
    --expect-input u-boot.bin -O u-boot.bin.signed

--allow-unverified downgrades both checks to warnings — for a legacy service that reports no digest, or alongside --skipBase64 when you want the stored representation rather than the signed artifact.

Exporting a verification public key: product getpubkey

product getpubkey exports the public key of one signing operation, so a build can embed it into a kernel, device tree or firmware image and the device can verify what that operation signed. It reports the SHA-256 of the key's DER SubjectPublicKeyInfo.

(venv) $ signing-tool -n test.ini product getpubkey \
    -P adcb30d8-f009-438e-b1b2-96f507b306cb \
    --operid 4add90e9-ffb3-4708-9554-ed2e82e8fd71 \
    -O uboot-verify.pem
public key written to: uboot-verify.pem
SHA-256 (DER SubjectPublicKeyInfo): 9f2b…c41d

--fingerprint <sha256> makes the export fail unless the key matches, so a rotated key breaks the build instead of being injected silently (case-insensitive):

(venv) $ signing-tool -n test.ini product getpubkey \
    -P "$PRODUCT" --operid "$OPER" \
    --fingerprint 9f2b...c41d -O uboot-verify.pem

Intended use is producing a reviewed, pinned bundle — not a live fetch on every build. It works for operations whose token carries a SubjectPublicKeyInfo (SignKernel, SignUBoot); other operation types give a clear error. It does not do X.509 conversion (use your board's own tool, e.g. fdt_add_pubkey) and does not resolve indexed HAB/SPSDK SRK chain material.

Exporting AES product keys

For products with exportable AES keys, a registered SecurityEngineer client can pull them out with secrets getkeys. Other key material is not exported this way — see the note in Clients. Keys are wrapped per-key with an ephemeral AES KEK derived via ECDH+HKDF against the client's EC private key, and the whole response is delivered inside a JWE addressed to that key. See Clients for the full scheme and registration steps.

(venv) $ signing-tool -n test.ini secrets getkeys \
    -P 6f603401-6723-4dec-a4a3-a8749865b46d \
    -C client.ec.private -O /tmp/prod.keys

# Add --cloudhsm when the deployment is backed by AWS CloudHSM
(venv) $ signing-tool -n test.ini secrets getkeys \
    -P 6f603401-6723-4dec-a4a3-a8749865b46d \
    -C client.ec.private -O /tmp/prod.keys --cloudhsm

One <prefix><i>.bin file is written per wrapped key. Asymmetric keys are additionally written as PKCS#8 / SubjectPublicKeyInfo PEM pairs (<prefix><i>.priv.pem, <prefix><i>.pub.pem); symmetric keys are raw bytes.

Output files and permissions

  • Exported key material and secrets are written 0600; public certificates and product getpubkey PEMs 0644; downloaded signed artifacts follow the umask (0644 under the usual 022).
  • Exports of secret or verification material never overwrite an existing path — they fail instead. The re-fetchable outputs are the exception: the JSON from product get -O / group get -O and the signed artifact from imagesigning get -O are refreshed in place.

Material exported by versions before 3.9.0 may have been world-readable

Before 3.9.0, exported secrets (private keys, KEK-IVs, SRK hashes, fusemaps, unwrapped key material) were written with default permissions rather than 0600. If you exported secrets with an earlier version, treat that material as having been world-readable and rotate as appropriate.

Proxies, internal CAs and timeouts

For enterprise networks:

Variable Effect
https_proxy / HTTPS_PROXY Honoured for API calls (not just the S3 upload/download legs)
no_proxy / NO_PROXY Honoured; port-qualified entries (sign.internal:8443) and bare IPv6 (::1) both match
REQUESTS_CA_BUNDLE / SSL_CERT_FILE Trust an internal CA without disabling TLS verification — prefer this over --skipssl

Every network call is bounded (10s connect / 60s read for API calls, 10s / 300s for S3 transfers), so a call can no longer hang indefinitely.

End-to-end CI example

This shows the exit codes doing their job — submit, wait for a human approval, download only the artifact signed over the exact input, then pin the verification key.

#!/bin/sh
set -eu
URL=https://app.laavat.io/<CustomerName>/api/v1
TOKENFILE=/run/secrets/jwt          # chmod 600
PRODUCT=adcb30d8-f009-438e-b1b2-96f507b306cb
OPER=4add90e9-ffb3-4708-9554-ed2e82e8fd71

# Behind a proxy or an internal CA:
# export https_proxy=http://proxy.internal:3128
# export NO_PROXY=app.laavat.io
# export REQUESTS_CA_BUNDLE=/etc/ssl/certs/internal-ca.pem

# 1. Submit. --json gives one document; the id comes out of it, not out of prose.
ID=$(signing-tool --json -c -a "$URL" -t @"$TOKENFILE" \
       imagesigning add SignUBoot \
       -N "build-$CI_PIPELINE_ID" -D "u-boot" \
       -P "$PRODUCT" --operid "$OPER" -F u-boot.imx \
    | jq -r .id)
echo "request $ID submitted"

# 2. Wait for the human approval, then download. --expect-input refuses a
#    signature over anything but the bytes we still hold.
set +e
signing-tool -c -a "$URL" -t @"$TOKENFILE" \
   imagesigning get -I "$ID" --wait --wait-timeout 1800 \
   --expect-input u-boot.imx -O u-boot.imx.signed
rc=$?
set -e
case $rc in
0) echo "signed artifact: u-boot.imx.signed" ;;
3) echo "still awaiting approval; re-run with the same id ($ID) to resume" >&2
    exit 1 ;;
4) echo "request $ID was rejected or failed" >&2
    exit 1 ;;
*) echo "signing failed (exit $rc)" >&2
    exit 1 ;;
esac

# 3. Pin the verification key that goes into the image.
signing-tool -c -a "$URL" -t @"$TOKENFILE" \
   product getpubkey -P "$PRODUCT" --operid "$OPER" \
   --fingerprint "$EXPECTED_PUBKEY_SHA256" -O uboot-verify.pem