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.jsonplus 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).
- 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.
- 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.jsonor a binary referenced by somecsfs[].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
idis omitted, ids are assigned automatically ascsf-0,csf-1, … - All CSFs in a request share the SRK table and the
IMG+CSFkeys 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.blocksout — it must be absent; - keep
mode: "hab4-spl"(the default whenautois set) —autois supported for SPL only; - you may also leave
signatureOffsetout — 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
binaryFilenameis 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.jsonnor referenced by a CSF. - A CSF's
authenticate.blocksentry 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.sourceIndexdisagrees between CSFs, or with the request'ssigningKeyIndex.modeisahaborahab-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
SGKkey.
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>