> Markdown version of https://docs.laavat.io/usage/signing-encryption/cstsigning/ from the LAAVAT PKI and Signing Platform documentation. All pages: https://docs.laavat.io/llms.txt

# 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](https://docs.laavat.io/usage/signing-encryption/cstsigning/#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](https://docs.laavat.io/usage/signing-encryption/cstsigning/#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](https://docs.laavat.io/usage/productguide/product/#imx6-based-gateway-product).
   i.MX8M uses the same HAB tree with an i.MX8M-specific fusemap — see
   [i.MX8M based products](https://docs.laavat.io/usage/productguide/product/#imx8m-based-products).

### AHAB based products

> **Note: Use SPSDK for new AHAB products**
> New products should sign AHAB containers with the `SignSPSDK` operation — see
> [SPSDK signing](https://docs.laavat.io/usage/signing-encryption/spsdksigning/). The CST-based AHAB signing described here is
> kept for products created earlier and is being phased out.

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](https://docs.laavat.io/usage/productguide/product/#imx8qxp-imx9-based-product).

## 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](https://docs.laavat.io/usage/signing-encryption/cstsigning/#raw-binary-input).

### Input archive layout

```text
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`:

```json
{
  "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[]`:

```json
{
  "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](https://docs.laavat.io/usage/signing-encryption/cstsigning/#authenticateauto)). `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](https://docs.laavat.io/usage/signing-encryption/cstsigning/#sizing-the-csf-region). |
| `output` | string | no | `patched` | `patched` \| `raw` — see [Output modes](https://docs.laavat.io/usage/signing-encryption/cstsigning/#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](https://docs.laavat.io/usage/signing-encryption/cstsigning/#example-usage) below). By default it is returned as-is; with
`outputEncoding: "base64"` it is returned base64-encoded. Layout:

```text
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:

```json
{
  "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](https://docs.laavat.io/usage/signing-encryption/cstsigning/#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](https://docs.laavat.io/exampleflows/habsigning/#archive-mode-signing-requestjson).

## 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 two config files created with `config-init`: `test.ini` for the regular
user and `approver.ini` for a user in the approvers group. Each config references
a `token_file` rather than embedding a token, so no token appears on the command
line — see [Reference Client › Usage](https://docs.laavat.io/democlient/usage/) for how to create
them and for the secure token-input forms (`-t @file`, `-t @-`).

```bash
(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 the signing request
(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 --skipBase64
```

For a full walkthrough, see the [HAB usage example](https://docs.laavat.io/exampleflows/habsigning/).

### Example usage: AHAB (i.MX9)

> CST-based AHAB signing is being phased out. For new products, sign AHAB
> containers with [SPSDK signing](https://docs.laavat.io/usage/signing-encryption/spsdksigning/) instead.

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

```bash
(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 imx9-container.bin

# Approve the signing request
(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 --skipBase64
```

For a full walkthrough, see the [AHAB usage example](https://docs.laavat.io/exampleflows/ahabsigning/).

### 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:

```text
[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:

```text
[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:

```text
[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>
```
