Skip to content

Building CRA-ready i.MX8M gateways

Secure boot and trusted firmware updates, end to end, on an NXP i.MX8M gateway. The boot ROM verifies the bootloader through HAB, u-boot verifies the kernel FIT, OP-TEE verifies its Trusted Applications, and RAUC verifies every update bundle — and every signing key stays inside the HSM.

The Cyber Resilience Act's Annex I requires that a product protects the integrity of its software and that security updates can be installed securely. On an i.MX8M gateway both come down to the same discipline: every stage of the boot chain and every update bundle is signed, and each is verified by the stage that loads it against a trust anchor placed on the device once. This guide explains what that takes, and how the manufacturer operates it on the LAAVAT platform in the most common configuration.

Scope

This guide covers the signing of the boot chain and of RAUC update bundles, the keys and certificate hierarchy behind them, and how the device verifies the result. It does not cover the update delivery infrastructure — hawkBit or another deployment server, the download and rollout process, or the device-side slot, rollback and health-check policies; those are configured in RAUC and the fleet management system independently of how bundles are signed. Applications delivered as containers on top of the root filesystem are a separate trust chain with its own signing and verification tooling; the OCI container signing example describes it.

Secure boot and secure firmware update

Secure boot means a device only runs software whose signature it can verify, starting from code that cannot be changed. The chain begins in the processor's boot ROM, which verifies the first bootloader stage against a key hash burned into one-time-programmable fuses; that stage verifies the next, and so on through the bootloader, the operating system kernel and the root filesystem. Each stage carries a signature made by the manufacturer and is checked against a trust anchor that an earlier stage brought with it. Break any link and the device stops, or refuses the image, rather than running modified code.

Secure firmware update extends the same rule to the software installed after the device has left the factory. An update bundle is signed by the manufacturer, verified by the device against a certificate chain it already trusts, and installed atomically into an inactive slot, so that a failed or rejected update leaves the running system untouched and the device can fall back.

The two are inseparable. Secure boot without secure update leaves no safe way to fix a vulnerability; secure update without secure boot verifies the bundle once and then trusts whatever is in flash at the next power-on. Together they are secure by design in the Act's sense: integrity is a property of the product's architecture and lifecycle, not a check added after release. What runs on the device is what the manufacturer released, from the first instruction to the latest update.

What it takes

None of the mechanisms have to be invented. The i.MX8M boot ROM implements HAB and verifies the first stage in silicon; u-boot verifies FIT images; OP-TEE verifies its Trusted Applications; the Linux kernel enforces dm-verity; RAUC installs verified A/B updates. These are mature, widely deployed and openly documented, and a Yocto-based BSP integrates them. The building blocks are not the hard part. The hard part is everything around them that has to be right for the lifetime of the product, and it falls into three areas.

  • Key management. The Super Root Keys behind the fuse hash must exist for the lifetime of the product and must never leak; a compromised SRK cannot be replaced on devices already in the field. The FIT, TA and update signing keys must be protected just as well, used only through controlled, approved and logged operations, and never present on a developer's machine.
  • Root-of-trust delivery. Every verifier needs its anchor delivered securely and embedded at the right point: the SRK hash to the manufacturing line for fusing, the kernel-signing public key into the u-boot control DTB, the TA-signing key into OP-TEE core, the update CA chain into the root filesystem. Each of these is a build-time input, and each must be the genuine one.
  • Signing the artifacts. Every release must have the bootloader stages, the kernel FIT, the Trusted Applications and the update bundle signed with the correct tool for each format, in the right order, with an audit trail of who signed what.

This is where LAAVAT comes in. The manufacturer keeps the design of its chain of trust and its build; LAAVAT provides the platform that holds the keys in an HSM, runs each signing operation with role-based access, approval and audit, operates the certificate hierarchy behind the update bundles, and delivers the trust anchors the device and the production line need.

Each artifact on the gateway is verified by the component that loads it, against a trust anchor placed on the device once. The table shows, link by link, what verifies what, where the anchor lives, and what LAAVAT does — and does not do — for it. The design of the chain, the build, the placement of the trust anchors and the verification on the device remain with the manufacturer, and nothing on the device talks to LAAVAT at boot or at update time.

Artifact Verified by Trust anchor on the device LAAVAT role
Bootloader: SPL + u-boot FIT Boot ROM, via HAB4 SRK fuses (hash of the SRK table) Signs with NXP CST, provides the SRK table and fuse hash
Kernel FIT u-boot Public key in the u-boot control DTB Signs with mkimage, provides the public key
Root filesystem Kernel via dm-verity Root hash in the signed kernel FIT None
Kernel modules Kernel module loader Per-build key built into the kernel None; the key is generated by the build
OP-TEE Trusted Applications OP-TEE core Public key built into OP-TEE core Signs with sign_encrypt.py, provides the public key
RAUC update bundle RAUC on the device CA keyring in the rootfs Bundles and signs with a one-time key, provides the CA keyring

Core property — HSM-resident keys

The four Super Root Keys, the kernel and OP-TEE signing keys and the RAUC issuing CA all live in the HSM and never leave it. What the device holds is public material only: the SRK hash in fuses, two public keys in the boot image, and a CA chain in the root filesystem. LAAVAT is not in the loop at boot or at update time. See key custody for how the private keys are held.

The manufacturer brings the build artifacts; LAAVAT returns them signed, together with the material the device needs to verify them.

graph LR
    subgraph you["You bring"]
        B1["<b>Secure boot artifacts</b><br/>SPL · u-boot FIT<br/>kernel FIT · OP-TEE TAs"]
        B2["<b>Update bundle</b><br/>rootfs · appfs · manifest<br/>verity format"]
    end
    subgraph laavat["LAAVAT platform"]
        L1["<b>Secure-boot signing</b><br/>SignHAB · SignKernel<br/>OP-TEESigning"]
        L2["<b>RAUC bundle signing</b><br/>one-time signer<br/>under your RAUC CA"]
    end
    subgraph out["Signed and verifiable"]
        O1["<b>Signed boot artifacts</b><br/>+ SRK fuse hash<br/>+ FIT and OP-TEE keys"]
        O2["<b>Signed RAUC bundle</b><br/>verified on the device<br/>against the CA keyring"]
    end
    B1 --> L1 --> O1
    B2 --> L2 --> O2

One product, two flows, with the same workflow and governance applied to both: HSM-protected keys, role-based access, an approval gate and a full audit trail on every operation.

Where signing happens

Signing is part of the release build. The CI pipeline produces the artifacts and calls the LAAVAT platform for each one; the signed results are what gets flashed at the factory and what gets shipped as field updates. In a typical integration this happens through the LAAVAT-provided Yocto scripts, which wrap the platform calls into the build. This guide shows the same steps as explicit signing-tool commands so that each one is visible.

graph TD
    A["<b>Build outputs</b><br/>SPL + u-boot FIT · kernel FIT · OP-TEE TAs · RAUC bundle input"]
    B["<b>CI pipeline</b><br/>signing-tool, or the LAAVAT Yocto scripts"]
    C["<b>i.MX8M Gateway product</b><br/>SignHAB · SignKernel · OP-TEESigning · RaucBundleSigningWithOneTimeKey"]
    D["<b>Boot image</b><br/>factory flash"]
    E["<b>Kernel FIT + TAs</b><br/>in the boot image / rootfs"]
    F["<b>RAUC bundle</b><br/>field update"]
    A --> B --> C
    C --> D
    C --> E
    C --> F

The LAAVAT product

A LAAVAT product groups the keys and the signing operations for one device type. The i.MX8M Gateway product carries the HAB key tree (four Super Root Keys, each with a CSF and an IMG signing certificate), HSM tokens for FIT and OP-TEE signing, and the RAUC issuing CA. Certificate profiles define what each certificate looks like; the product template references them by id.

Certificate profiles

Four profiles are needed: a Root and an End-Entity profile for the HAB tree, and a Sub CA and an End-Entity profile for the RAUC hierarchy. They are added through the console or with signing-tool:

printf '%s' "$TOKEN" | signing-tool -c -t @- -a https://app.laavat.io/<tenant>/api/v1 \
    profile add -F imx8m-habv4-rootca-profile.yaml -N "i.MX8M HABv4 Root CA Profile" -T ROOT
printf '%s' "$TOKEN" | signing-tool -c -t @- -a https://app.laavat.io/<tenant>/api/v1 \
    profile add -F imx8m-habv4-endentity-profile.yaml -N "i.MX8M HABv4 End-Entity Profile" -T END
# the two RAUC profiles likewise; each upload returns the profile id used in the product template

Validity periods

The HAB certificates are deliberately long-lived: the SRKs cannot be replaced on fused devices, and the boot ROM does not evaluate certificate validity. The RAUC validities are examples; an issuer must not expire before the certificates it issues.

i.MX8M HABv4 Root CA Profile

Name: i.MX8M HABv4 Root CA Profile
DN:
  Organization: Manufacturer
ProfileType: 3                 # Root
SignatureAlgorithm: 4          # SHA256WithRSA
KeyAlgorithm: 1                # RSA
ValidityPeriod: "1752000h"     # 200 years
BasicConstraints:
  BasicConstraintsValid: true
  IsCA: true
  MaxPathLen: 0
  MaxPathLenZero: false
AddSKI: true
AddAKI: true
KeyUsage:
  - 32                         # CertSign
ExtendedKeyUsage:

i.MX8M HABv4 End-Entity Profile

Name: i.MX8M HABv4 End-Entity Profile
DN:
  Organization: Manufacturer
ProfileType: 1                 # End entity (CSF and IMG certificates)
SignatureAlgorithm: 4          # SHA256WithRSA
KeyAlgorithm: 1                # RSA
ValidityPeriod: "1752000h"     # 200 years
BasicConstraints:
  BasicConstraintsValid: true
  IsCA: false
AddSKI: true
AddAKI: true
KeyUsage:
  - 1                          # DigitalSignature
ExtendedKeyUsage:
ExtraExtensions: true
SANUsage: true

i.MX8M RAUC Sub CA Profile

Name: i.MX8M RAUC Sub CA Profile
DN:
  Organization: Manufacturer
  Country: FI
ProfileType: 2                 # Sub CA
SignatureAlgorithm: 10         # ECDSAWithSHA256
KeyAlgorithm: 2                # ECDSA
ValidityPeriod: "219000h"      # 25 years, example
BasicConstraints:
  BasicConstraintsValid: true
  IsCA: true
  MaxPathLen: 0                # issues end-entity certificates only
  MaxPathLenZero: true
AddSKI: true
AddAKI: true
KeyUsage:
  - 32                         # CertSign
  - 64                         # CRLSign
ExtendedKeyUsage:

i.MX8M RAUC End-Entity Profile

Name: i.MX8M RAUC End-Entity Profile
DN:
  Organization: Manufacturer
  Country: FI
ProfileType: 1                 # End entity (ephemeral bundle signer)
SignatureAlgorithm: 10         # ECDSAWithSHA256
KeyAlgorithm: 2                # ECDSA
ValidityPeriod: "87600h"       # 10 years, example
BasicConstraints:
  BasicConstraintsValid: true
  IsCA: false
AddSKI: true
AddAKI: true
KeyUsage:
  - 1                          # DigitalSignature
  - 2                          # ContentCommitment
ExtendedKeyUsage:
  - 3                          # CodeSigning
ExtraExtensions: true
SANUsage: true

Example product definition

The product is created from the JSON template below with signing-tool and approved in the console. Identifiers are placeholders; the group ids are the RBAC groups allowed to submit and to approve each operation. In a CI setup the release group may also be listed under blanketGroups for auto-approval.

printf '%s' "$TOKEN" | signing-tool -c -t @- -a https://app.laavat.io/<tenant>/api/v1 \
    product add -T imx8m-gateway-product.json
{
  "name": "i.MX8M Gateway",
  "description": "i.MX8M-based reference gateway product. Covers HAB secure boot (SPL + u-boot), FIT kernel signing, OP-TEE Trusted Application signing and RAUC bundle signing.",
  "productType": "Production",
  "enabled": true,
  "caInfo": [
    {
      "CN": "SRK0",
      "description": "CA used for i.MX8M HAB",
      "useCase": "HABCA",
      "profileID": "<hab-root-profile-uuid>",
      "keyType": "RSA4096",
      "certificateType": "ROOT",
      "leafs": [
        { "CN": "CSF0", "description": "CSF End-Entity certificate", "useCase": "HABCA",
          "keyType": "RSA4096", "certificateType": "ENDENTITY", "profileID": "<hab-ee-profile-uuid>" },
        { "CN": "IMG0", "description": "IMG signing certificate", "useCase": "HABCA",
          "keyType": "RSA4096", "certificateType": "ENDENTITY", "profileID": "<hab-ee-profile-uuid>" }
      ]
    },
    {
      "CN": "SRK1",
      "description": "CA used for i.MX8M HAB",
      "useCase": "HABCA",
      "profileID": "<hab-root-profile-uuid>",
      "keyType": "RSA4096",
      "certificateType": "ROOT",
      "leafs": [
        { "CN": "CSF1", "description": "CSF End-Entity certificate", "useCase": "HABCA",
          "keyType": "RSA4096", "certificateType": "ENDENTITY", "profileID": "<hab-ee-profile-uuid>" },
        { "CN": "IMG1", "description": "IMG signing certificate", "useCase": "HABCA",
          "keyType": "RSA4096", "certificateType": "ENDENTITY", "profileID": "<hab-ee-profile-uuid>" }
      ]
    },
    {
      "CN": "SRK2",
      "description": "CA used for i.MX8M HAB",
      "useCase": "HABCA",
      "profileID": "<hab-root-profile-uuid>",
      "keyType": "RSA4096",
      "certificateType": "ROOT",
      "leafs": [
        { "CN": "CSF2", "description": "CSF End-Entity certificate", "useCase": "HABCA",
          "keyType": "RSA4096", "certificateType": "ENDENTITY", "profileID": "<hab-ee-profile-uuid>" },
        { "CN": "IMG2", "description": "IMG signing certificate", "useCase": "HABCA",
          "keyType": "RSA4096", "certificateType": "ENDENTITY", "profileID": "<hab-ee-profile-uuid>" }
      ]
    },
    {
      "CN": "SRK3",
      "description": "CA used for i.MX8M HAB",
      "useCase": "HABCA",
      "profileID": "<hab-root-profile-uuid>",
      "keyType": "RSA4096",
      "certificateType": "ROOT",
      "leafs": [
        { "CN": "CSF3", "description": "CSF End-Entity certificate", "useCase": "HABCA",
          "keyType": "RSA4096", "certificateType": "ENDENTITY", "profileID": "<hab-ee-profile-uuid>" },
        { "CN": "IMG3", "description": "IMG signing certificate", "useCase": "HABCA",
          "keyType": "RSA4096", "certificateType": "ENDENTITY", "profileID": "<hab-ee-profile-uuid>" }
      ]
    },
    {
      "CN": "RAUC Issuing Sub CA",
      "description": "Sub CA that issues the ephemeral RAUC bundle-signer certificates. Provisioned with externalRoot: the CSR is signed by the RAUC Root CA and the certificate is uploaded to this slot.",
      "useCase": "RaucCodeSigning",
      "keyType": "ECDSAP256",
      "certificateType": "SUBCA",
      "externalRoot": true,
      "profileID": "<rauc-subca-profile-uuid>"
    }
  ],
  "rndKeys": [],
  "productOperations": [
    {
      "name": "HAB CST signing",
      "description": "HAB CST signing for SPL and u-boot images (i.MX8M).",
      "operationType": "SignHAB",
      "approvalRule": {
        "name": "HAB signing approval rule",
        "description": "Approval rule for HAB CST signing.",
        "allowedGroups":  ["<release-group-uuid>"],
        "approvalGroups": ["<approver-group-uuid>"],
        "blanketGroups":  []
      }
    },
    {
      "name": "Kernel signing",
      "description": "FIT image kernel signing.",
      "operationType": "SignKernel",
      "token": { "name": "Kernel", "description": "HSM token for FIT image kernel signing.", "keyType": "RSA2048" },
      "approvalRule": {
        "name": "Kernel signing approval rule",
        "description": "Approval rule for kernel FIT signing.",
        "allowedGroups":  ["<release-group-uuid>"],
        "approvalGroups": ["<approver-group-uuid>"],
        "blanketGroups":  []
      }
    },
    {
      "name": "OP-TEE TA image signing",
      "description": "OP-TEESigning operation for .ta artifacts (RSA-PSS-SHA256, 4096-bit).",
      "operationType": "OP-TEESigning",
      "token": { "name": "OP-TEE signing key", "description": "RSA 4096 key for OP-TEE TA signing.", "keyType": "RSA4096" },
      "approvalRule": {
        "name": "OP-TEE signing approval rule",
        "description": "Approval rule for OP-TEE TA signing.",
        "allowedGroups":  ["<release-group-uuid>"],
        "approvalGroups": ["<approver-group-uuid>"],
        "blanketGroups":  []
      }
    },
    {
      "name": "RAUC bundle signing",
      "description": "RAUC update-bundle CMS signing (ECDSA P-256). Each call generates an ephemeral bundle-signer key in the HSM, has the RAUC Issuing Sub CA issue its certificate using the End-Entity profile, signs the bundle, and discards the key.",
      "operationType": "RaucBundleSigningWithOneTimeKey",
      "profileID": "<rauc-ee-profile-uuid>",
      "token": { "name": "RAUC one-time signing key", "description": "Ephemeral key for RAUC bundle-signer certificates.", "keyType": "ECDSAP256" },
      "approvalRule": {
        "name": "RAUC signing approval rule",
        "description": "Approval rule for RAUC bundle signing.",
        "allowedGroups":  ["<release-group-uuid>"],
        "approvalGroups": ["<approver-group-uuid>"],
        "blanketGroups":  []
      }
    }
  ],
  "productConfigItems": [
    { "name": "FusemapType", "value": "IMX8" }
  ]
}

FusemapType: IMX8 matters: it is what makes SignHAB recalculate the Image Vector Table for FIT images, which i.MX8M requires. See Product management for the configuration items.

The RAUC update PKI

Update bundles are verified on the device against a CA chain, so they need a PKI of their own.

Why the Sub CA is marked externalRoot in the product template

A CA declared in a product template is normally created self-signed, as a root that belongs to that product. For the RAUC Sub CA that is the wrong shape: the device keyring should anchor on a Root that lives outside any product, stays untouched by day-to-day signing, and can sign a replacement Sub CA later without a new keyring on fielded devices. externalRoot: true tells the platform to generate the Sub CA's key and a certificate signing request instead of a self-signed certificate; the request is then signed by the RAUC Root CA and the resulting certificate uploaded to the Sub CA slot. The Sub CA stays inside the product, where the RAUC operation can use it, but its trust comes from the Root.

  • RAUC Root CA


    Outside the product

    ECDSA P-256, self-signed, created in the console. Signs the Sub CA's CSR once and is otherwise untouched.

    On the device — in the keyring

  • RAUC Issuing Sub CA


    Inside the product

    ECDSA P-256, externalRoot, MaxPathLen 0. Issues one certificate per bundle signing.

    On the device — in the keyring

  • Bundle signer


    Ephemeral

    ECDSA P-256, CN = random UUID, EKU CodeSigning. Key discarded after use.

    On the device — no; travels inside each bundle

Creating the hierarchy

  1. Upload the two RAUC profiles (Sub CA and End-Entity).
  2. Create the RAUC Root CA in the console as a self-signed Root, using a Root profile of the same key type, and approve it.
  3. Create the product from the template. The RAUC Issuing Sub CA appears in pending state, holding a CSR.
  4. Download the Sub CA's CSR. The download is the DER request encoded as base64; convert it to PEM before use:

    printf '%s' "$TOKEN" | signing-tool -c -t @- -a https://app.laavat.io/<tenant>/api/v1 \
        ca getcsr --caid <rauc-subca-uuid> -O "RAUC Issuing Sub CA.csr"
    base64 -d < "RAUC Issuing Sub CA.csr" | openssl req -inform DER -outform PEM -out subca.csr.pem
    
  5. On the RAUC Root CA choose Issue certificate, paste the PEM CSR and select the RAUC Sub CA profile, then approve the request. The result is a PKCS#7 containing the Sub CA certificate and the Root.

  6. Upload that certificate to the pending Sub CA. It becomes active, chained under the Root.

The console dialogs are the same as for the device-identity hierarchy; Trusted device identities walks through them. The externally rooted Sub CA pattern is also shown in the advanced PKI example flow.

From then on every RAUC signing request generates a fresh key inside the HSM, has the Sub CA issue a certificate for it, signs the bundle, and discards the key. Re-signing an existing bundle works the same way and leaves the payload, manifest and verity data byte-identical.

Signing the release artifacts

In a typical integration the LAAVAT-provided Yocto scripts perform these steps inside the build. They are shown here as explicit signing-tool commands for clarity. The pattern is the same for every operation: submit the input, poll until the request is Ready, download the result. With an approval rule the request waits in ApprovalRequired until an approver releases it in the console; with blanket approval the platform signs immediately.

Token handling

Every command below reads the bearer token from $TOKEN on stdin with -t @- and targets the tenant's API with -a. A config file created with config-init works equally well — see secure token handling.

export TOKEN=<bearer-token>

# poll a request until Ready (state 16); 2 = ApprovalRequired, 8 = InProcess, 32/64 = Rejected/Failed
printf '%s' "$TOKEN" | signing-tool -c -t @- -a https://app.laavat.io/<tenant>/api/v1 \
    --json imagesigning get -I <request-uuid> | jq .state

imagesigning get --wait blocks until the request is ready rather than polling by hand — see Reference client usage.

Bootloader: SPL and u-boot in one request — SignHAB

The combined boot image carries two HAB images, SPL at offset 0 and u-boot at its own IVT. Both are signed in one archive-mode request: a flat .tar.gz holding the binary and a request.json with one CSF per stage. The authenticate blocks are the regions the ROM hashes; the SPL CSF also carries the Unlock MID command. The offsets come from the BSP build — the CSF files CST would have used.

{
  "csfs": [
    {
      "id": "spl",
      "binaryFilename": "imx-boot-gateway.bin",
      "unlock": { "features": ["MID"] },
      "signatureOffset": "0x31e00",
      "authenticate": { "blocks": [
        { "address": "0x91ffc0", "offset": "0x0", "length": "0x31e00" } ] }
    },
    {
      "id": "uboot",
      "binaryFilename": "imx-boot-gateway.bin",
      "signatureOffset": "0x59020",
      "csfRegionSize": "0x1FE0",
      "authenticate": { "blocks": [
        { "address": "0x401fcdc0", "offset": "0x58000",  "length": "0x1020"  },
        { "address": "0x40200000", "offset": "0x5B000",  "length": "0xD6B88" },
        { "address": "0x402D6B88", "offset": "0x131B88", "length": "0x9568"  },
        { "address": "0x970000",   "offset": "0x13B0F0", "length": "0xA150"  } ] }
    }
  ]
}
tar -czf boot-signing.tar.gz request.json imx-boot-gateway.bin

printf '%s' "$TOKEN" | signing-tool -c -t @- -a https://app.laavat.io/<tenant>/api/v1 \
    imagesigning add SignHAB -P <product-uuid> --operid <signhab-operation-uuid> \
    -N "gateway-1.4.0-boot" -D "SPL + u-boot, release 1.4.0" -F boot-signing.tar.gz
Request sent. Request ID: <request-uuid>, state: Created

printf '%s' "$TOKEN" | signing-tool -c -t @- -a https://app.laavat.io/<tenant>/api/v1 \
    imagesigning get -I <request-uuid> --skipBase64 -O boot-signed.tar.gz
Downloading signed binary to: boot-signed.tar.gz
File Downloaded

tar -tzf boot-signed.tar.gz
response.json
signed/imx-boot-gateway.bin

response.json lists one entry per CSF; the last entry's sha256_output is the SHA-256 of the delivered binary. Each stage now ends with a CSF: the SRK table, the CSF and IMG certificates, and two CMS signatures. A single image (an SPL on its own, a u-boot FIT on its own) can also be submitted raw, without a request file; the platform reads the IVT and signs the one image it finds, and returns the result base64-encoded. The request format is documented in full in CST signing.

Kernel FIT — SignKernel

The FIT is submitted as-is. The platform signs every configuration's signature node with the product's Kernel key (mkimage) and returns the re-packed FIT. The signature node's algorithm and key-name-hint come from the FIT itself, so the build's .its must declare the key size the product actually has: a FIT declaring sha256,rsa4096 signed with an RSA-2048 key cannot verify.

printf '%s' "$TOKEN" | signing-tool -c -t @- -a https://app.laavat.io/<tenant>/api/v1 \
    imagesigning add SignKernel -P <product-uuid> --operid <signkernel-operation-uuid> \
    -N "gateway-1.4.0-kernel" -D "kernel FIT, release 1.4.0" -F fitImage
Request sent. Request ID: <request-uuid>, state: Created

printf '%s' "$TOKEN" | signing-tool -c -t @- -a https://app.laavat.io/<tenant>/api/v1 \
    imagesigning get -I <request-uuid> --skipBase64 -O fitImage.signed

See FIT signing for preparing the .its and embedding the public key into u-boot.

OP-TEE Trusted Applications — OP-TEESigning

Each .ta is submitted raw; the platform re-signs the TA header with the product's OP-TEE key (RSA-PSS, SHA-256) and returns it. The payload hash inside the header is unchanged; only the signature differs.

printf '%s' "$TOKEN" | signing-tool -c -t @- -a https://app.laavat.io/<tenant>/api/v1 \
    imagesigning add OP-TEESigning -P <product-uuid> --operid <optee-operation-uuid> \
    -N "gateway-1.4.0-ta" -D "TA <ta-uuid>, release 1.4.0" -F <ta-uuid>.ta
Request sent. Request ID: <request-uuid>, state: Created

printf '%s' "$TOKEN" | signing-tool -c -t @- -a https://app.laavat.io/<tenant>/api/v1 \
    imagesigning get -I <request-uuid> --skipBase64 -O <ta-uuid>.signed.ta

RAUC update bundle — RaucBundleSigningWithOneTimeKey

The input is a .tar.gz with a request.json and a directory holding the manifest and the images. The verity format is the usual choice: the root filesystem is protected by a dm-verity hash tree, and the manifest carrying the tree's root hash is signed.

# request.json
{ "format": "verity", "directory": "data" }

# data/manifest.raucm
[bundle]
format=verity

[update]
compatible=gateway-v1
version=1.4.0

[image.rootfs]
filename=rootfs.ext4
tar -czf update-1.4.0.tar.gz request.json data/

printf '%s' "$TOKEN" | signing-tool -c -t @- -a https://app.laavat.io/<tenant>/api/v1 \
    imagesigning add RaucBundleSigningWithOneTimeKey -P <product-uuid> --operid <rauc-operation-uuid> \
    -N "gateway-1.4.0-update" -D "RAUC bundle, release 1.4.0" -F update-1.4.0.tar.gz
Request sent. Request ID: <request-uuid>, state: Created

printf '%s' "$TOKEN" | signing-tool -c -t @- -a https://app.laavat.io/<tenant>/api/v1 \
    imagesigning get -I <request-uuid> --skipBase64 -O gateway-1.4.0.raucb

The result is the .raucb bundle with an inline CMS signature made by the ephemeral signer. The signer's certificate is embedded in the bundle; the Sub CA and Root are not, which is why the device keyring must contain both. The input formats — plain, verity, crypt, and re-signing — are documented in RAUC signing.

What goes on the device

Trust anchor Source on the platform Where it goes
SRK fuse hash Product secrets (SRKHASH, with the SRK table as SRKTABLE) Burned into the SRK_HASH OTP fuses at manufacturing. One-time; verify against the signed image before burning
Kernel public key product getpubkey for the SignKernel operation u-boot control DTB, key node named after the FIT's key-name-hint, required = "conf"
OP-TEE public key product getpubkey for the OP-TEESigning operation Built into OP-TEE core (TA verification key)
RAUC CA keyring Certificate chain of the RAUC Issuing Sub CA (Sub CA + Root) /etc/rauc/keyring.pem, referenced from system.conf

SRK fuse hash

The SRK table and its hash are part of the product's secrets. They are retrieved with a registered manufacturing client (client certificate authentication), which keeps the fuse material off developer accounts:

printf '%s' "$TOKEN" | signing-tool -c -t @- -a https://app.laavat.io/<tenant>/api/v1 \
    secrets get -P <product-uuid> -C client.private -O product-secrets.json
jq -r .SRKHASH product-secrets.json
<64 hex characters>

How the hash is burned depends on the fusing tool in the production line; the value and its format are described in the HAB signing example flow and CST signing. Registering the client is covered in Clients.

FIT and OP-TEE public keys

printf '%s' "$TOKEN" | signing-tool -c -t @- -a https://app.laavat.io/<tenant>/api/v1 \
    product getpubkey -P <product-uuid> --operid <signkernel-operation-uuid> -O kernel-pub.pem
public key written to: kernel-pub.pem
SHA-256 (DER SubjectPublicKeyInfo): <64 hex characters>

printf '%s' "$TOKEN" | signing-tool -c -t @- -a https://app.laavat.io/<tenant>/api/v1 \
    product getpubkey -P <product-uuid> --operid <optee-operation-uuid> -O optee-pub.pem

kernel-pub.pem is wrapped into the u-boot control DTB by the BSP build (mkimage -K, key node key-<hint> with required = "conf"); optee-pub.pem is the TA_PUBLIC_KEY OP-TEE core is built with.

RAUC keyring

The keyring is the Sub CA certificate together with the Root. Export the RAUC Issuing Sub CA's certificate from the console as PKCS#7 (the export carries the chain), or take it from the product secrets, then convert to PEM:

# from the console export
openssl pkcs7 -in rauc-ca-chain.p7 -print_certs -out keyring.pem

# or from the product secrets (caInfo entry of the RAUC Issuing Sub CA)
jq -r '.caInfo[] | select(.cn=="RAUC Issuing Sub CA") | .pkcs7chain' product-secrets.json \
    | base64 -d | openssl pkcs7 -inform DER -print_certs -out keyring.pem

openssl x509 -in keyring.pem -noout -subject   # first cert
subject=C = FI, O = Manufacturer, CN = RAUC Issuing Sub CA

Install it on the device and reference it from RAUC's system.conf:

[keyring]
path=/etc/rauc/keyring.pem
check-purpose=codesign

Verifying before shipping

Everything the device will check can be checked on a build host first, with the same tools, against the public material LAAVAT provides. The gateway release pipeline can do this as a smoke test after every signing round.

Boot image (HAB)

# one run per stage; the offsets are the IVT locations from the request: SPL at 0x0, u-boot at 0x59000
hab4_image_verifier signed/imx-boot-gateway.bin 0x0
  IVT Tag: 0xD1
  CSF Length: 88 bytes
    Command Tag: 0xBE   Processing 'Install Key' command...        (SRK table)
    Command Tag: 0xBE   Processing 'Install Key' command...        (CSF certificate)
    Command Tag: 0xCA   Processing 'Authenticate Data' command...  (CSF signature)
    Command Tag: 0xB2   Processing 'Unlock' command...
    Command Tag: 0xBE   Processing 'Install Key' command...        (IMG certificate)
    Command Tag: 0xCA   Processing 'Authenticate Data' command...
      Block Start: 0x91FFC0   Block Length: 204288 bytes
    Command Tag: 0xB2   Processing 'Unlock' command...
  SRK Table Hash: <64 hex characters>
  Signature 1:  issuer: O=Manufacturer, CN=SRK0
  Signature 2:  issuer: O=Manufacturer, CN=SRK0

What to check: the SRK Table Hash equals the product's SRKHASH; the authenticate blocks match the request; both stages carry two signatures issued by the intended SRK. This confirms structure and key material only: the signature itself is verified by the boot ROM, and no host tool stands in for it.

Kernel FIT

fit_check_sign -f fitImage.signed -k kernel-verify.dtb
Signature check OK
Verifying Hash Integrity for node 'conf-gateway.dtb'... sha256,rsa2048:<hint>+
Verified OK, loading images

kernel-verify.dtb is a control DTB carrying kernel-pub.pem with required = "conf", the same form u-boot uses. The unsigned input must fail against it (Signature check bad); that is what proves the check has teeth.

OP-TEE Trusted Application

python3 sign_encrypt.py verify --uuid <ta-uuid> --in <ta-uuid>.signed.ta --key optee-pub.pem
TA UUID: <ta-uuid>

python3 sign_encrypt.py display --in <ta-uuid>.signed.ta | grep hash
hash: <64 hex characters>   (identical to the input's)

RAUC bundle

rauc info -c system.conf gateway-1.4.0.raucb
Verifying bundle signature...
Verified inline signature by 'C = FI, O = Manufacturer, CN = <signer-uuid>'
Compatible:     'gateway-v1'
Version:        '1.4.0'
Bundle Format:  verity
  Verity Hash:  '<64 hex characters>'
Certificate Chain:
 0 Subject: C = FI, O = Manufacturer, CN = <signer-uuid>
   Issuer:  C = FI, O = Manufacturer, CN = RAUC Issuing Sub CA
 1 Subject: C = FI, O = Manufacturer, CN = RAUC Issuing Sub CA
   Issuer:  C = FI, O = Manufacturer, CN = RAUC Root CA
 2 Subject: C = FI, O = Manufacturer, CN = RAUC Root CA
   Issuer:  C = FI, O = Manufacturer, CN = RAUC Root CA

rauc extract -c system.conf gateway-1.4.0.raucb /tmp/bundle   # verifies the hash tree as well

system.conf here is the device's, or a minimal copy with the keyring path and check-purpose=codesign. A bundle with a modified payload fails on rauc extract; a bundle signed under a different CA fails already on rauc info.

Where off-device verification stops

Worth stating plainly, because a passing smoke test is easy to over-read:

Claim Off-device What would close the gap
Kernel FIT signature is cryptographically correct for u-boot Fully proven
TA signature is cryptographically correct for OP-TEE core Fully proven
Bundle signature chains to the keyring and the hash tree is intact Fully proven
HAB signatures are correct for the boot ROM Structure and key material only The ROM is the only verifier; boot on fused silicon
The SRK hash in fuses matches the signed image Not tested Read back the fuses on the production line before closing them
dm-verity holds at mount time and the slot switch completes Not tested rauc install on a device, then reboot into the new slot

Mapping to the CRA

Annex I requirement Covered by
Integrity of the software and its configuration is protected; unauthorised modification is detected HAB secure boot from ROM; signed kernel FIT and TAs; dm-verity root filesystem
Security updates are provided and can be installed securely RAUC bundles signed under the manufacturer's own CA, verified on the device before installation
Keys are protected; access is controlled and logged All signing keys in the HSM; per-operation roles, approval gates and audit trail on the platform
Products are identifiable and can be onboarded securely IDevID and LDevID issuance — see Trusted device identities

Building a device this way does not by itself make a product CRA compliant. The Annex I mapping sets out which requirements the platform supports and which remain yours.

References