Skip to content

CST signing

CST signing protects the boot chain of i.MX based devices. It uses NXP's Code Signing Tool (CST) with the device's High Assurance Boot (HAB) or Advanced High Assurance Boot (AHAB) keys. To use it, first create a product whose configuration includes the matching (A)HAB tree and the SignHAB operation — signing then runs against that product.

CST signing accepts two input formats, and the same SignHAB operation handles both:

  • Archive-mode input — an archive containing a request.json plus one or more binaries. Use it to sign one or several binaries, each with one or more CSFs, in a single request. Archive mode covers HAB4.
  • Raw-binary input — a single SPL, FIT, or AHAB binary on its own (SPL = Secondary Program Loader, FIT = Flattened Image Tree). Use it for AHAB containers and for simple single-binary HAB signing.

The platform tells the two apart automatically: a request.json inside an archive selects archive mode, and anything else is treated as a raw binary. No extra API field is needed.

Product configuration

Both input formats sign against a product that carries the appropriate key tree and the SignHAB operation. Create the product before submitting a signing request.

HAB based products

For i.MX6, i.MX7, and i.MX8M devices, CST signing uses the HAB tree generated during product creation — the CSF and IMG keys. By default these come from the first Super Root Key slot (SRK0), but the slot is configurable: the client selects any of SRK0–SRK3 per request with signingKeyIndex (and per CSF with srk.sourceIndex in archive mode).

  1. Create a product for the HAB tree and CST signing operation. See product for CST signing. i.MX8M uses the same HAB tree with an i.MX8M-specific fusemap — see i.MX8M based products.

AHAB based products

For i.MX8 QuadMax / QuadXPlus and i.MX9 devices, CST signing uses the AHAB tree generated during product creation — the SGK key. As with HAB, the SRK slot defaults to SRK0 (SRK0→SGK0) but is configurable with signingKeyIndex, selecting any of SRK0–SRK3.

  1. Create a product for the AHAB tree and CST signing operation. See product for CST signing.

Archive-mode input

The payload is a .tar, .tar.gz, or .zip archive containing a top-level request.json plus the binary file(s) to sign. A single request can carry several CSFs across one or more binaries.

Archive mode currently supports HAB4 only (mode: "hab4" and mode: "hab4-spl"). For AHAB, use Raw-binary input.

Input archive layout

input.tar.gz
├── request.json
├── u-boot.bin                  # one or more binaries referenced from csfs[]
└── ...

Constraints:

  • Flat layout only — no subdirectories, no symlinks, no hardlinks.
  • Every file in the archive must be either request.json or a binary referenced by some csfs[].binaryFilename. Unreferenced files are rejected.
  • Archive limits: ≤ 64 entries, ≤ 1 GiB per file, ≤ 2 GiB total.
  • request.json ≤ 256 KiB.

request.json

Each entry in the csfs[] array describes one CSF (Command Sequence File — the signed command block the device's boot ROM runs to authenticate the image). List a single entry to sign once, or several entries to sign multiple times — at different offsets in the same binary, or across different binaries.

A single binary can be signed more than once: add two or more entries with the same binaryFilename and different signatureOffset values. Each entry produces its own signature, and the platform patches them all into one output copy of the binary at signed/<binaryFilename>.

The example below signs both the SPL stage and the u-boot stage of one u-boot.bin:

{
  "csfs": [
    {
      "id": "spl",
      "mode": "hab4-spl",
      "binaryFilename": "u-boot.bin",
      "signatureOffset": "0x20000",
      "csfRegionSize": "0x2000",
      "unlock": { "features": ["MID"] },
      "authenticate": {
        "blocks": [
          { "address": "0x87800000", "offset": "0x0", "length": "0x20000" }
        ]
      }
    },
    {
      "id": "uboot",
      "mode": "hab4",
      "binaryFilename": "u-boot.bin",
      "signatureOffset": "0x180000",
      "authenticate": {
        "blocks": [
          { "address": "0x80000000", "offset": "0x40000",  "length": "0x800"   },
          { "address": "0x80100000", "offset": "0x80000",  "length": "0x80000" },
          { "address": "0x80180000", "offset": "0x100000", "length": "0x8000"  },
          { "address": "0x80188000", "offset": "0x108000", "length": "0x4000"  }
        ]
      }
    }
  ]
}

To sign just once, use a single-entry csfs[]:

{
  "csfs": [
    {
      "mode": "hab4-spl",
      "binaryFilename": "u-boot.bin",
      "signatureOffset": "0x20000",
      "authenticate": {
        "blocks": [
          { "address": "0x87800000", "offset": "0x0", "length": "0x20000" }
        ]
      }
    }
  ]
}

Rules:

  • Up to 16 CSFs per request.
  • If id is omitted, ids are assigned automatically as csf-0, csf-1, …
  • All CSFs in a request share the SRK table and the IMG + CSF keys of the product.

Field reference

A field name containing a dot describes a nested JSON object — the part before the dot is the object key. For example unlock.features is written "unlock": { "features": ["MID"] }, authenticate.blocks is "authenticate": { "blocks": [ … ] }, srk.sourceIndex is "srk": { "sourceIndex": 0 }, and installKey.targetIndex is "installKey": { "targetIndex": 2 }.

Field Type Required Default Allowed values / notes
id string no csf-N ^[A-Za-z0-9_-]{1,64}$; unique within csfs[].
mode string no hab4 (or hab4-spl when authenticate.auto: true) hab4 | hab4-spl.
binaryFilename string yes ^[A-Za-z0-9][A-Za-z0-9._-]{0,254}$; must reference a file in the archive.
engine string no CAAM CAAM | CAAM-HSM | RTIC.
version string no 4.3 4.1 | 4.2 | 4.3.
hashAlgorithm string no sha256 sha256 | sha384 | sha512.
srk.sourceIndex int no 0 0..3. Must agree with the request's signingKeyIndex (if set) and with all other CSFs in the request.
installKey.verificationIndex int no 0 0..7[Install Key] verification slot.
installKey.targetIndex int no 2 0..7[Install Key] target slot.
authenticate.auto bool no false If true, the blocks are read from the binary's HAB IVT (Image Vector Table) header (see authenticate.auto). blocks must then be absent.
authenticate.blocks []object yes when auto: false Each entry: { "address": "0x…", "offset": "0x…", "length": "0x…" }. offset + length must not exceed the binary size.
authenticate.verificationIndex int no 2 0..7[Authenticate Data] verification slot.
unlock.features []string no (no [Unlock] block) At most one of MID, RNG, OCOTP, MFG.
signatureOffset string (hex) conditional File offset where the signature is written. Required for output: "patched" unless authenticate.auto: true. Must be empty for output: "raw".
csfRegionSize string (hex) no Optional. If set, the signature is padded to this size with 0xFF and an output larger than it is rejected; if omitted, only the signed bytes are written (no padding). Must be empty for output: "raw". See Sizing the CSF region.
output string no patched patched | raw — see Output modes.
outputEncoding string no raw Top-level field on the request (not per-CSF). raw returns the archive as-is; base64 returns it base64-encoded.

Hex values (addresses, offsets, lengths) may be upper- or lower-case but must start with 0x.

The SRK slot (0–3) used for signing is set by the request-level signingKeyIndex parameter (provided as optionalParameters.signingKeyIndex on the signing request). A CSF's optional srk.sourceIndex may restate it but must match it — request.json does not override the request parameter. When neither is set, slot 0 is used.

authenticate.auto

For an SPL image you can let the platform read the authenticated blocks straight from the binary instead of listing them by hand. Set authenticate.auto: true and:

  • leave authenticate.blocks out — it must be absent;
  • keep mode: "hab4-spl" (the default when auto is set) — auto is supported for SPL only;
  • you may also leave signatureOffset out — for SPL the platform derives it from the IVT header.

For u-boot or FIT images, list the blocks explicitly with authenticate.blocks; auto does not apply there.

Sizing the CSF region

csfRegionSize is optional. When omitted, only the signed CSF bytes are written at signatureOffset — no padding — and that length is reported as signatureSize. Set it only when your boot image reserves a fixed-size region for the CSF and the output must be padded with 0xFF to exactly that size, so the next stage stays at its fixed offset.

To choose a value, take the gap your flash layout reserves between signatureOffset and the start of the next region. It must be at least the produced CST output (a larger output is rejected with CST output exceeds csfRegionSize) and must not run into an authenticated block or another CSF's signature region (overlaps are rejected). A HAB4 CSF is a few KiB, so a reserved region of 0x2000 is typical; a signature placed immediately before the next block uses the exact gap instead (for example 0x1FE0). Must be empty for output: "raw".

Output modes

output controls how each CSF's signature is returned:

output Result
patched (default) The signature is written into the binary at signatureOffset. The patched binary appears at signed/<binaryFilename>.
raw The signature is returned on its own as signatures/<id>.sig, and the binary is left untouched. signatureOffset and csfRegionSize must be empty.

Use raw when you want to place the signature into the binary yourself — for example when your build inserts it somewhere other than a single in-place patch.

Output archive

The signed result is a .tar.gz archive, downloaded with the get operation (see the example below). By default it is returned as-is; with outputEncoding: "base64" it is returned base64-encoded. Layout:

signed.tar.gz
├── signed/
│   └── u-boot.bin              # one entry per binary that had at least one patched CSF
├── signatures/
│   ├── spl.sig                 # one entry per CSF with output: "raw"
│   └── uboot.sig
└── response.json

response.json is always present. signed/ appears only when at least one CSF was patched into a binary; signatures/ appears only when at least one CSF used output: "raw". A request whose CSFs are all raw therefore has no signed/ directory.

response.json

Per-CSF metadata describing the result:

{
  "version": "1",
  "csfs": [
    {
      "id": "spl",
      "binaryFilename": "u-boot.bin",
      "mode": "hab4-spl",
      "sha256_input":  "f3a1…",
      "sha256_output": "9b22…",
      "patched": true,
      "signatureOffset": "0x20000",
      "signatureSize": 4096
    },
    {
      "id": "uboot",
      "binaryFilename": "u-boot.bin",
      "mode": "hab4",
      "sha256_input": "f3a1…",
      "patched": false,
      "signaturePath": "signatures/uboot.sig",
      "signatureSize": 4096
    }
  ]
}
Field When present Notes
id always From the request, or auto-assigned.
binaryFilename always Echoed from the request.
mode always Echoed from the request.
sha256_input always SHA-256 of the original binary, before patching.
sha256_output only if patched: true SHA-256 of signed/<binaryFilename> as of this CSF's patch. When several CSFs patch one binary, only the last one's value matches the delivered file.
patched always true if the signature was patched into the binary.
signatureOffset only if patched: true Echoed from the request.
signatureSize always Bytes written at signatureOffset (padded to csfRegionSize when set), or the size of the detached .sig.
signaturePath only if patched: false signatures/<id>.sig.

Note

sha256_output is the SHA-256 of signed/<binaryFilename> as of that CSF's patch — verify a delivered binary by comparing sha256sum signed/<binaryFilename> against it, not by hashing the signed.tar.gz (the same result can produce slightly different archive bytes from one run to the next). When a binary is signed by several CSFs the patches accumulate in one file, applied in csfs[] order, so only the last CSF for that binary matches the delivered file — earlier values are intermediate hashes. sha256_output is present only for patched CSFs; an output: "raw" CSF yields a detached .sig and no sha256_output.

Validation

The request is fully validated before any signing starts. It is rejected when:

  • csfs[] is missing or empty.
  • There are unknown JSON keys, or extra data after the request body.
  • A binaryFilename is missing from the archive, or uses a forbidden pattern (leading dot, leading dash, .., or path separators).
  • The archive contains a file that is neither request.json nor referenced by a CSF.
  • A CSF's authenticate.blocks entry overlaps any CSF's signature region on the same binary (the signature would overwrite signed bytes).
  • Two CSFs' signature regions on the same binary overlap.
  • srk.sourceIndex disagrees between CSFs, or with the request's signingKeyIndex.
  • mode is ahab or ahab-spl — AHAB is not available in archive mode; use Raw-binary input.

Example usage

For a full archive-mode walkthrough — building the archive, submitting the request, and downloading the result — see the HAB usage example.

Raw-binary input

The other format is a single raw binary on its own — no archive, no request.json. The platform works out what to do from the binary and the product's keys:

  • HAB4 (SPL or FIT) for i.MX6, i.MX7, and i.MX8M — the stage is detected from the IVT header.
  • AHAB for i.MX8 QuadMax / QuadXPlus and i.MX9 — used when the product carries an SGK key.

Input data

The input is a single SPL image, FIT image, or AHAB container. For AHAB, sign one container at a time — if several containers need signing, submit them one by one.

Example usage: HAB (i.MX6)

NXP i.MX6 packages can be signed with the CST tool based operation. The examples below use $TOKEN for a regular user token and $APPROVERTOKEN for a token belonging to a user in the approvers group:

(venv) $ signing-tool -c -t $TOKEN -a https://app.laavat.io/<CustomerName>/api/v1 imagesigning add SignHAB \
    -N test -D test2 -P adcb30d8-f009-438e-b1b2-96f507b306cb --operid 4add90e9-ffb3-4708-9554-ed2e82e8fd71 -F SPL-hab

# Approve the signing request
(venv) $ signing-tool -c -t $APPROVERTOKEN -a https://app.laavat.io/<CustomerName>/api/v1 imagesigning \
    approve -I a83081a6-1d3b-4117-a81b-0ebcfcf0669c

# Get the signed payload
(venv) $ signing-tool -c -t $TOKEN -a https://app.laavat.io/<CustomerName>/api/v1 imagesigning \
    get -I a83081a6-1d3b-4117-a81b-0ebcfcf0669c -O /tmp/signed.bin --skipBase64

For a full walkthrough, see the HAB usage example.

Example usage: AHAB (i.MX9)

NXP i.MX9 containers are signed with the same operation; the input is an AHAB container (for example imx9-container.bin):

(venv) $ signing-tool -c -t $TOKEN -a https://app.laavat.io/<CustomerName>/api/v1 imagesigning add SignHAB \
    -N test -D test2 -P adcb30d8-f009-438e-b1b2-96f507b306cb --operid 4add90e9-ffb3-4708-9554-ed2e82e8fd71 -F imx9-container.bin

# Approve the signing request
(venv) $ signing-tool -c -t $APPROVERTOKEN -a https://app.laavat.io/<CustomerName>/api/v1 imagesigning \
    approve -I a83081a6-1d3b-4117-a81b-0ebcfcf0669c

# Get the signed payload
(venv) $ signing-tool -c -t $TOKEN -a https://app.laavat.io/<CustomerName>/api/v1 imagesigning \
    get -I a83081a6-1d3b-4117-a81b-0ebcfcf0669c -O /tmp/signed.bin --skipBase64

For a full walkthrough, see the AHAB usage example.

CSF templates (reference)

For reference, these are the CST descriptor templates the platform fills in for each image type. For HAB, the system detects whether the input is an SPL or FIT image and uses the matching template.

SPL CSF template:

[Header]
Version = 4.1
Hash Algorithm = sha256
Engine Configuration = 0
Certificate Format = X509
Signature Format = CMS
Engine = <ENGINE>

[Install SRK]
File = "<SRK_TABLE_PATH>"
Source index = 0

[Install CSFK]
File = "pkcs11:token=<TOKEN>;object=<CSF_X509_LABEL>;type=cert;pin-source=./token"

[Authenticate CSF]

[Unlock]
Engine = CAAM
Features = MID

[Install Key]
# Key slot index used to authenticate the key to be installed
Verification index = 0

# Target key slot in HAB key store where key will be installed
Target index = 2
# Key to install
File = "pkcs11:token=<TOKEN>;object=<IMG_X509_LABEL>;type=cert;pin-source=./token"

[Authenticate Data]
Verification index = 2

#        Address      Offset     Length       Data File Path
Blocks = <BLOCKS> "<FILENAME>"

FIT CSF template:

[Header]
Version = 4.1
Hash Algorithm = sha256
Engine Configuration = 0
Certificate Format = X509
Signature Format = CMS
Engine = <ENGINE>

[Install SRK]
File = "<SRK_TABLE_PATH>"
Source index = 0

[Install CSFK]
File = "pkcs11:token=<TOKEN>;object=<CSF_X509_LABEL>;type=cert;pin-source=./token"

[Authenticate CSF]

[Install Key]
# Key slot index used to authenticate the key to be installed
Verification index = 0

# Target key slot in HAB key store where key will be installed
Target index = 2
# Key to install
File = "pkcs11:token=<TOKEN>;object=<IMG_X509_LABEL>;type=cert;pin-source=./token"

[Authenticate Data]
Verification index = 2

#        Address      Offset     Length       Data File Path
Blocks = <BLOCKS> "<FILENAME>"

AHAB CSF template:

[Header]
Target = AHAB
Version = 1.0

[Install SRK]
# SRK table generated by srktool
File = "<SRK_TABLE_PATH>"
# Public key certificate in PEM format
Source = "pkcs11:token=<TOKEN>;object=<SRK_X509_LABEL>;type=cert;pin-source=./token"
# Index of the public key certificate within the SRK table (0 .. 3)
Source index = 0
# Type of SRK set (NXP or OEM)
Source set = OEM
# bitmask of the revoked SRKs
Revocations = 0x0

# Optional subordinate SGK key
[Install Certificate]
# Public key certificate in PEM format
File = "pkcs11:token=<TOKEN>;object=<SGK_X509_LABEL>;type=cert;pin-source=./token"
# bitmask of the permissions
Permissions = 0x1

[Authenticate Data]
# Binary to be signed generated by mkimage
File = "<FILENAME>"
# Offsets = Container header  Signature block (printed out by mkimage)
Offsets   = <BLOCKS>