LabWired The easy way to build hardware
The nRF52840 secure-boot lab produces CRA evidence in CI.

CRA secure-boot evidence in CI

Category: Security

The European Union Cyber Resilience Act (CRA) requires manufacturers to document product security. Secure boot, signed updates, and rollback protection are part of that documentation.

The automotive sector has related requirements in UNECE R155 and R156. Vehicle type approval assesses the UNECE R155 and R156 requirements.

This example uses LabWired to run repeatable security checks in continuous integration (CI). The same virtual board model runs in the Playground and in headless CI.

The example has three parts:

  1. Model the security path. A virtual Nordic nRF52840 uses a virtual ATECC608A secure element over I²C. Firmware provisions a root key. Firmware verifies signed updates. Firmware rejects older versions.
  2. Check exact state. The CI test checks UART output and write-once memory. The CI test checks AES output and secure-element responses.
  3. Export evidence. Each successful labwired-cra-evidence run uploads claims and logs. The run uploads hashes of the inputs and hashes of the outputs. The run uploads a signed run record and the public keys. The job creates temporary private keys. The job deletes the temporary private keys after use.

This evidence covers only the tested secure-boot and update path. This evidence does not cover the full CRA Annex I. This evidence does not provide a software bill of materials (SBOM). This evidence does not provide a process that handles vulnerabilities. This evidence does not provide disclosure processes. This evidence does not provide support-period records.

Most products in the CRA scope use manufacturer self-assessment. Annex III and Annex IV products can require a Notified Body. The evidence pack can support a technical file. The evidence pack is not a certificate. The evidence pack is not a legal opinion.

One bench run gives weak evidence

A manual board test records one result at one point in time. A manual board test does not run again when the firmware changes.

A CI pipeline is easier to audit than a manual board test. Each successful run leaves assertions, logs, and digests. A failed security check blocks the pipeline. The repository does not need to store an original-equipment-manufacturer (OEM) private key.

The lab

The source is in labwired-core under examples/nrf52840-secure-boot-lab. The lab contains a virtual nRF52840 and an ATECC608A model on I²C. The lab contains open firmware and a smoke script. The smoke script reads memory.

The firmware completes three boots.

Boot 1: provision. The nRF52840 has User Information Configuration Registers (UICR). The model allows the bits to change from 1 to 0. The model does not allow the bits to change back to 1.

The firmware gets a root key from the simulator’s seeded random-number generator. The fixed seed makes CI runs repeatable. Firmware writes the key to the UICR. Firmware sets the anti-rollback counter to version 1. Firmware writes the APPROTECT debug-lock value. Firmware prints ROT: PROVISIONED. Firmware reboots.

Boot 2: check boot and install a signed update. Firmware runs an AES-128-ECB challenge. OpenSSL calculates the same value on the CI host. The test requires the firmware result and the OpenSSL result to match. Firmware reports SECURE BOOT OK (v1) after the two results match.

The test then sends a 140-byte over-the-air (OTA) update over UART. Firmware hashes the package with SHA-256. The secure element verifies the ECDSA P-256 signature of the OEM with the stored public key. Firmware writes the update to flash. Firmware changes the counter to version 2. Firmware reboots.

Boot 3: enforce policy and attest. Firmware starts as version 2. Firmware hashes the update slot again. Firmware then rejects a correctly signed version 1 package because the package is older. Firmware also rejects a forged version 3 package. The secure element then signs a challenge with the device key. Firmware verifies the signature.

UART text from a successful run:

ROT: PROVISIONED
SECURE BOOT OK (v1)
OTA v2 SIGNATURE OK
OTA v2 COMMITTED
SECURE BOOT OK (v2)
ANTI-ROLLBACK v2 ACTIVE
UPDATE SLOT VERIFIED
ROLLBACK REJECTED
BAD SIGNATURE REJECTED
ATTESTATION OK

UART text alone is not sufficient evidence. Firmware could print these lines and not do the checks.

The smoke script also reads memory. The smoke script checks the UICR root-key words and the version counter. The smoke script checks the AES ciphertext and the secure-element result bytes. The smoke script checks the package hashes in RAM.

The evidence covers only checked behavior.

MechanismWhat the lab runsWhat the mechanism is not
Root keyThe UICR bits change from 1 to 0.The UICR is simulated. The UICR is not write-once silicon.
Boot checkThe test checks AES-ECB against OpenSSL.The check is a demo challenge. The check is not a full measured boot.
Secure elementThe secure element performs real P-256 ECDSA.The model runs the commands that this lab uses. The model is not the full datasheet.
Anti-rollbackThe counter only increases.The counter uses the same simulated UICR.
Debug lockThe firmware writes the APPROTECT value.The model stores the value. The model does not enforce the value.

Playground

The same firmware drives an SSD1306 on the shared I²C bus. Watch the panel show each stage in this order: provision, OTA verify, commit, rejects, and CRA READY. Tap the MCU for Serial, Registers, Trace, or Memory.

Open the full playground · Open the playground with the Serial panel open

Evidence pack contents

A successful LabWired/labwired-cra-evidence run uploads an artifact named cra-evidence-pack:

FileContents
claims.json / claims.mdEach claim has a link to the UART assertions and the memory assertions that passed.
run-manifest.jsonThe file lists the inputs of the run and a hash of the outputs.
run-manifest.digest + .sigThe digest file contains the hash. The signature file contains a signature of the hash.
pack-signing-pubkey.pemThe file contains the public key that signed this run.
result.json, uart.log, junit.xmlThe files contain the raw outputs from the lab.
oem-verify-pubkey.hexThe test checks the updates against this public key.
limitations.mdThe file lists the limits. The pack contains the file.

Claim IDs include otp_root_key_provisioned, aes_boot_challenge, and ecdsa_ota_verify. The pack creates a claim only when the assertion for that claim in result.json passes.

The claims state which checks ran. The claims do not state that the product meets all CRA requirements.

Ephemeral keys

The CI job handles temporary keys as follows:

  1. CI runs make_packages.py --ephemeral.
  2. OpenSSL creates a P-256 key pair in a temporary directory.
  3. The script signs three packages. The first package is a valid version 2 package. The second package is an old version 1 package. The third package is a forged version 3 package.
  4. The script writes the public key to system.yaml as oem_pubkey_hex for the secure-element model.
  5. The script deletes the temporary directory and the private key.
  6. The evidence pack keeps oem-verify-pubkey.hex.

A second temporary key signs the run record. The pack keeps only the public key of the second temporary key.

A production system should use a hardware security module (HSM) and a controlled, long-lived company key. This example shows the test flow. This example is not a production key-management design.

Do not commit a private-key PEM file to either repository.

Limits

Run the example

Create an evidence pack:

git clone https://github.com/LabWired/labwired-cra-evidence
cd labwired-cra-evidence
./scripts/run_evidence.sh
# → out/.../cra-evidence-pack/

You can also download cra-evidence-pack from a successful Actions run.

Run only the lab from labwired-core:

cargo build -p firmware-nrf52840-secure-boot --release --target thumbv7em-none-eabi
cargo run -q -p labwired-cli -- test \
  --script examples/nrf52840-secure-boot-lab/secure-boot-smoke.yaml \
  --output-dir out/nrf52840-secure-boot

Repository: LabWired/labwired-cra-evidence.

Andrii Shylenko
Andrii Shylenko

Founder, LabWired.