1. Today’s topic

Today we examine security mechanisms:

text
Secure Boot        — permits only trusted firmware to boot
Flash Encryption   — hides external Flash contents
TrustZone          — isolates code and data at runtime
Secure Storage     — protects keys and secret parameters
Anti-rollback      — forbids revoked old versions from booting

The central idea: Secure Boot protects the boot chain, TrustZone protects the runtime access boundary, and Flash encryption protects data when the device is powered off. None replaces the others.

2. Why this matters in your projects

Firmware acquires sensitive assets:

text
MQTT client private key
APN and server passwords
command-signing keys
configuration-encryption keys
device certificates
safe OTA parameters
anti-replay counters
factory calibration
critical GPIO and power outputs

An AT-parser bug may allow code execution that reads the MQTT private key. MPU helps detect memory violations but does not create a complete cryptographic boundary. TrustZone on STM32U5/H5/Cortex-M33 can separate keys and secure services from the normal application. The classic ESP32-WROOM-32U has no TrustZone; its foundation is Secure Boot, Flash Encryption, NVS Encryption, eFuse, unique device keys, and architectural access restrictions.

3. Theory

6.3.1 3.1. Threat model

Before enabling security flags, define:

text
what we protect;
who we protect it from;
in which device state;
what loss is acceptable.

6.3.2 3.2. Secure Boot

Chain of trust:

text
ROM / immutable Root of Trust
→ verify bootloader
→ verify application
→ begin execution

Secure Boot verifies code authenticity. It does not hide firmware contents.

6.3.3 3.3. Flash Encryption

Flash Encryption hides external Flash contents. Without Secure Boot, however, it does not guarantee that the executing code is trusted.

text
Secure Boot without Flash Encryption:
  code is verified, but may be read from Flash.
Flash Encryption without Secure Boot:
  code is hidden, but chain authenticity is weaker.

6.3.4 3.4. TrustZone

On Cortex-M33, the system can be split:

text
Secure world:
  keys;
  crypto;
  OTA verification;
  secure storage;
  critical counters;
  some peripherals.
NonSecure world:
  FreeRTOS;
  EC25 parser;
  MQTT/TCP;
  UI;
  normal logic.

The Secure API should be narrow:

c
typedef enum {
    SECURE_STATUS_OK = 0,
    SECURE_STATUS_INVALID_ARG,
    SECURE_STATUS_DENIED,
    SECURE_STATUS_INTERNAL_ERROR,
} secure_status_t;
secure_status_t secure_sign_event(const uint8_t *message,
                                  size_t message_size,
                                  uint8_t *signature,
                                  size_t signature_capacity,
                                  size_t *signature_size);

The private key is never returned outside. 6.3.5 3.5. A Secure Service validates arguments

The Secure world does not trust NonSecure input. Validate pointer, length, output capacity, state, role, rate limits, and replay. Poor API:

c
const uint8_t *secure_get_private_key(void);

Better API:

c
secure_status_t secure_hmac_calculate(secure_key_id_t key_id,
                                      const void *message,
                                      size_t message_size,
                                      void *output,
                                      size_t output_size);

6.3.6 3.6. What belongs in the Secure world

Suitable:

text
OTA signature verification
device identity key
HMAC/signing service
anti-replay counter
security version
factory provisioning state
secure storage
critical firmware confirmation operation

Unsuitable:

text
the whole MQTT client
JSON parser
EC25 AT parser
the whole FreeRTOS application
logger
web server
complex business logic

6.3.7 3.7. Anti-rollback

Distinguish:

text
firmware version:
  changes with each release
build number:
  grows monotonically
security version:
  changes only when revoking an old security generation

Do not increment secure_version for every normal release, or you will lose functional rollback.

6.3.8 3.8. Provisioning

text
DEVELOPMENT
→ test keys, debug open
PILOT
→ production-like keys, recovery test
PRODUCTION
→ final keys, eFuse/option bytes, provisioning report

The private signing key must not be stored in Git, a Docker image, a HIL Raspberry Pi, or a CI PR-job.

4. Common mistakes

text
1. Treating Flash Encryption as a substitute for Secure Boot.
2. Treating Secure Boot as protection of secrets in RAM.
3. Placing the whole project in the Secure world.
4. Giving NonSecure code a pointer to a key.
5. Not checking NonSecure pointer and length.
6. Keeping a signing key in an ordinary CI secret.
7. Enabling eFuse/option bytes on the only debug board.
8. Using one symmetric key for the whole fleet.
9. Putting a private key in logs or a core dump.
10. Not designing key rotation.

5. Practical assignment for 30-60 minutes

Create SECURITY_ARCHITECTURE.md:

markdown
# Security architecture
## Assets
| Asset | Confidential? | Integrity critical? | Location |
|---|---:|---:|---|
| Firmware signing private key | yes | yes | offline signing environment |
| Firmware verification key | no | yes | eFuse/bootloader |
| MQTT device private key | yes | yes | secure storage/encrypted NVS |
| CA certificate | no | yes | firmware/read-only storage |
| APN password | yes | medium | encrypted NVS |
| Phase policy | no | yes | signed config/read-only runtime |
| Boot counter | no | yes | protected persistent storage |

For ESP32, draw:

text
Untrusted/external:
  EC25 bytes, MQTT, HTTP, CLI
Application:
  parser, networking, phase logic
Security services:
  OTA verification, encrypted storage
Hardware root:
  eFuse, Secure Boot digest, flash encryption key

For STM32 TrustZone:

text
NonSecure:
  EC25, MQTT, Modbus, UI, phase logic
Secure:
  key operations, OTA verification,
  anti-replay counter, secure storage
Immutable Root:
  STiRoT/OEMiRoT

6. Further reading

  • ESP-IDF Security Overview, Secure Boot v2, and Flash Encryption.
  • STM32H5/U5 TrustZone, STiRoT/OEMiRoT, OBKeys, and secure storage.

Brief recap

text
immutable root
→ verified bootloader
→ verified application
→ Secure/NonSecure boundary
→ narrow secure services
→ isolated device keys
→ signed OTA
→ anti-rollback
→ controlled provisioning

Exercise

An application asks a secure service to sign arbitrary input and return a pointer to its private key. Redesign the interface and validation boundary.

Self-check criteria: Explain how an untrusted pointer and output capacity are checked; naming TrustZone alone does not validate an unsafe API.

Show the supplied answer

Never return the private key or a pointer to it. Provide a narrow operation such as signing a bounded digest, validate input/output ranges and lengths plus authorization/state/rate limits, and keep key operations inside the secure boundary.

Exercise

Distinguish firmware version, build number, and security version in a release policy that preserves functional rollback while revoking a compromised generation.

Self-check criteria: Describe the revocation decision and recovery/provisioning evidence before changing irreversible device settings.

Show the supplied answer

Firmware version identifies the feature release; build number grows monotonically; security version changes when an older security generation must be revoked. Avoid advancing the irreversible security threshold for every normal feature release.