Series Context: This is Part 15 of our 20-part CMSIS Mastery Series. Security is now a mandatory consideration for any connected embedded product — PSA Certified compliance is increasingly required by procurement and regulation. Part 3 (startup/vector table) and Part 16 (bootloaders) are closely related reading.
1
Overview & ARM Cortex-M Ecosystem
CMSIS layers, Cortex-M families, memory map, toolchains
Completed
2
CMSIS-Core: Registers, NVIC & SysTick
core_cmX.h, register access, interrupt controller, SysTick timer
Completed
3
Startup Code, Linker Scripts & Vector Table
Reset handler, BSS init, scatter files, boot process
Completed
4
CMSIS-RTOS2: Threads, Mutexes & Semaphores
Thread management, synchronization primitives, scheduling
Completed
5
CMSIS-RTOS2: Message Queues & Event Flags
Inter-thread comms, ISR-to-thread, real-time design patterns
Completed
6
CMSIS-DSP: Filters, FFT & Math Functions
FIR/IIR filters, FFT, SIMD optimizations
Completed
7
CMSIS-Driver: UART, SPI & I2C
Driver abstraction layer, callbacks, DMA integration
Completed
8
CMSIS-Pack & Software Components
Pack files, device support, dependency management
Completed
9
Debugging with CMSIS-DAP & CoreSight
SWD/JTAG, HardFault analysis, ITM tracing
Completed
10
Portable Firmware: Multi-Vendor Projects
HAL vs CMSIS, cross-platform BSPs, reusable libraries
Completed
11
Interrupts, Concurrency & Real-Time Constraints
Interrupt latency, critical sections, lock-free programming
Completed
12
Memory Management in Embedded Systems
Static vs dynamic, heap fragmentation, memory pools
Completed
13
Low Power & Energy Optimization
Sleep modes, clock gating, tickless RTOS, power profiling
Completed
14
DMA & High-Performance Data Handling
DMA basics, peripheral transfers, zero-copy techniques
Completed
15
Security: ARMv8-M & TrustZone
Secure/non-secure worlds, secure boot, firmware protection
You Are Here
16
Bootloaders & Firmware Updates
OTA updates, dual-bank flash, fail-safe strategies
17
Testing & Validation
Unity/Ceedling unit tests, HIL testing, integration testing
18
Performance Optimization
Compiler flags, inline assembly, cache (M7/M33), profiling
19
Embedded Software Architecture
Layered design, event-driven, state machines, component-based
20
Tooling & Workflow (Professional Level)
CI/CD for embedded, MISRA, static analysis, Doxygen
TrustZone for Cortex-M Overview
TrustZone for Cortex-M is ARM's hardware security technology introduced with the ARMv8-M architecture. Unlike TrustZone for Cortex-A (which targets application processors and is relatively heavyweight), TrustZone for Cortex-M is purpose-built for microcontrollers — minimal overhead, no hypervisor, no operating system required in the Secure world. A single physical processor executes in two distinct security states: Secure (S) and Non-Secure (NS).
The key insight is that the security boundary is enforced entirely in hardware. Even if the Non-Secure world is completely compromised by a remote attacker, it cannot access Secure memory regions, Secure peripherals, or Secure registers. Private keys, provisioning data, and cryptographic operations remain protected regardless of what runs in the NS world.
Hardware Security Model
The TrustZone hardware model adds a Security Attribution Unit (SAU) and relies on an optional implementation-defined Implementation-Defined Attribution Unit (IDAU) to assign every address in the 4 GB address space to one of three security states:
- Secure (S): accessible only from Secure state; NS accesses generate a SecureFault exception
- Non-Secure Callable (NSC): a gateway region — Secure code, but callable via
BLXNS from Non-Secure state through a veneer table
- Non-Secure (NS): accessible from both Secure and Non-Secure states
Mental Model: Think of the Secure world as a locked vault embedded inside the chip. The Non-Secure application knocks on the vault door (calls a veneer function) to request services — hashing, encryption, random numbers. The vault handler checks the request, performs the operation, and returns a result — but never reveals the keys.
TrustZone-Enabled Cortex-M Devices
| Core |
TrustZone Version |
SAU Regions |
FPU / Helium |
Example Devices |
| Cortex-M23 |
ARMv8-M Baseline |
Up to 8 |
No |
STM32L5, NXP LPC55S0x, Microchip SAML11 |
| Cortex-M33 |
ARMv8-M Mainline |
Up to 8 |
Optional FPv5-SP |
STM32U5, nRF9160, NXP LPC55S6x, Nordic nRF5340 |
| Cortex-M35P |
ARMv8-M Mainline |
Up to 8 |
Optional FPv5-SP |
Arm DesignStart (hardened pipeline) |
| Cortex-M55 |
ARMv8.1-M |
Up to 8 |
Yes (Helium MVE) |
STM32N6, Alif Ensemble |
| Cortex-M85 |
ARMv8.1-M |
Up to 8 |
Yes (Helium MVE) |
Renesas RA8, future high-performance MCUs |
SAU & IDAU Configuration
The Security Attribution Unit is programmed by Secure code (typically the secure bootloader or secure init code) before transitioning to Non-Secure world. It defines up to 8 regions, each with a base address, a limit address, and a security attribute (NS or NSC). Regions not covered by any SAU entry default to Secure unless the IDAU says otherwise.
Defining SAU Regions via CMSIS-Core
/**
* SAU configuration for a Cortex-M33 device (e.g., STM32U5 or NXP LPC55S69).
*
* Flash layout (example, 1 MB total):
* 0x00000000 – 0x0003FFFF Secure code (256 KB)
* 0x0003E000 – 0x0003FFFF NSC veneer table (8 KB, within Secure)
* 0x00040000 – 0x000FFFFF Non-Secure code (768 KB)
*
* SRAM layout (512 KB total):
* 0x20000000 – 0x2001FFFF Secure SRAM (128 KB)
* 0x20020000 – 0x2007FFFF Non-Secure SRAM (384 KB)
*/
#include "core_cm33.h"
void sau_init(void)
{
/* Disable SAU during configuration */
SAU->CTRL = 0u;
/* Region 0: Non-Secure Callable (NSC) — veneer table in flash */
SAU->RNR = 0u;
SAU->RBAR = 0x0003E000u; /* base (32-byte aligned) */
SAU->RLAR = (0x0003FFFFu & SAU_RLAR_LADDR_Msk) |
SAU_RLAR_NSC_Msk | /* NSC attribute */
SAU_RLAR_ENABLE_Msk;
/* Region 1: Non-Secure flash — application code */
SAU->RNR = 1u;
SAU->RBAR = 0x00040000u;
SAU->RLAR = (0x000FFFFFu & SAU_RLAR_LADDR_Msk) |
SAU_RLAR_ENABLE_Msk; /* NS (NSC bit clear) */
/* Region 2: Non-Secure SRAM */
SAU->RNR = 2u;
SAU->RBAR = 0x20020000u;
SAU->RLAR = (0x2007FFFFu & SAU_RLAR_LADDR_Msk) |
SAU_RLAR_ENABLE_Msk;
/* Region 3: Non-Secure peripherals (e.g., GPIO, UART accessible to NS) */
SAU->RNR = 3u;
SAU->RBAR = 0x40010000u; /* example NS peripheral base */
SAU->RLAR = (0x4001FFFFu & SAU_RLAR_LADDR_Msk) |
SAU_RLAR_ENABLE_Msk;
/* Enable SAU; regions not covered remain Secure by default */
SAU->CTRL = SAU_CTRL_ENABLE_Msk;
/* Enable SecureFault exception so we can handle illegal NS accesses */
SCB->SHCSR |= SCB_SHCSR_SECUREFAULTENA_Msk;
__DSB();
__ISB();
}
Non-Secure Callable Regions and the Veneer Table
The NSC region is a narrow section of Secure flash that holds veneer functions — small trampolines that the linker generates for each __attribute__((cmse_nonsecure_entry)) function. When NS code calls one of these veneers, the processor transitions to Secure state before reaching the actual implementation. Without the NSC gateway, any branch from NS into Secure memory raises a SecureFault.
Security Rule: The NSC region should be as small as possible — only the veneer trampolines, never actual Secure data or key material. Minimise the attack surface of the gateway.
Secure & Non-Secure Worlds
Memory Partitioning in Practice
A typical TrustZone project consists of two separate firmware images built independently: a Secure image and a Non-Secure image. The Secure image is linked to the Secure flash region and linked with a CMSE_veneer_output.o (or equivalent) that exports the veneer symbols for the NS image to link against.
# Build Secure image (Keil MDK / arm-none-eabi-gcc)
# Linker flag to generate the veneer import library:
# --cmse-implib --out-implib=secure_veneer.o
#
# Example GNU LD flags for secure project:
arm-none-eabi-gcc \
-mcpu=cortex-m33 -mthumb -mfpu=fpv5-sp-d16 -mfloat-abi=hard \
-mcmse \
-T secure_flash.ld \
-Wl,--cmse-implib,--out-implib=secure_veneer.o \
-o secure.elf \
secure_main.c sau_init.c secure_api.c
# Build Non-Secure image — links against the veneer import library
arm-none-eabi-gcc \
-mcpu=cortex-m33 -mthumb -mfpu=fpv5-sp-d16 -mfloat-abi=hard \
-T nonsecure_flash.ld \
-o nonsecure.elf \
nonsecure_main.c app_logic.c secure_veneer.o
Peripheral Security Assignment
On most ARMv8-M devices the security state of peripherals is configured via a Peripheral Protection Controller (PPC) or equivalent — a set of registers in the Secure domain that assigns each peripheral as Secure or Non-Secure. The Non-Secure world can only access peripherals explicitly assigned to it. Accessing a Secure peripheral from NS generates a BusFault or SecureFault depending on the device implementation.
/**
* Peripheral security assignment — STM32U5 example.
* GTZC (Global TrustZone Controller) controls peripheral security.
*/
#include "stm32u5xx.h"
void peripheral_security_init(void)
{
/* Enable GTZC clock */
__HAL_RCC_GTZC1_CLK_ENABLE();
/* Assign USART1 as Non-Secure (application can use it) */
GTZC_TZSC1->SECCFGR1 &= ~GTZC_CFGR1_USART1_Msk; /* clear = NS */
/* Keep RNG as Secure (only Secure world provides random numbers) */
GTZC_TZSC1->SECCFGR1 |= GTZC_CFGR1_RNG_Msk; /* set = Secure */
/* Keep AES as Secure */
GTZC_TZSC1->SECCFGR1 |= GTZC_CFGR1_AES_Msk;
/* Configure MPCBB: set SRAM1 pages 0-31 as Secure, pages 32-95 as NS */
for (uint32_t i = 0u; i < 32u; i++) {
GTZC_MPCBB1->SECCFGR[i / 32u] |= (1u << (i % 32u)); /* Secure */
}
}
Secure Entry Function Veneers
Veneer functions are the gatekeepers of the Secure world. They are the only sanctioned entry points from Non-Secure code into Secure code. Every secure API function intended to be called from NS must be marked with __attribute__((cmse_nonsecure_entry)). The compiler and linker generate a corresponding veneer stub in the NSC region automatically.
NSE Attribute & Parameter Sanitisation
/**
* Secure API — compiled as part of the Secure image.
* File: secure_api.c (linked into secure flash)
*/
#include "cmse_nonsecure_entry.h" /* or arm_cmse.h */
#include "psa/crypto.h" /* PSA Crypto API */
/* Secure key storage — never accessible from NS world */
static uint8_t s_device_key[32] = { /* provisioned at manufacture */ };
/**
* secure_get_random() — Non-Secure callable.
* Provides cryptographically secure random bytes from the Secure RNG.
*
* SECURITY: Validate that the NS pointer is a genuine NS address before
* dereferencing it. A malicious NS caller could pass a Secure address
* to trick the Secure function into revealing Secure data.
*/
__attribute__((cmse_nonsecure_entry))
int32_t secure_get_random(uint8_t *ns_buf, uint32_t len)
{
/* Validate pointer: must be wholly within Non-Secure address space */
if (cmse_check_pointed_object(ns_buf, CMSE_NONSECURE) == NULL) {
return -1; /* reject — pointer not in NS address space */
}
if (cmse_check_address_range(ns_buf, len, CMSE_NONSECURE) == NULL) {
return -1; /* reject — range extends into Secure space */
}
if (len == 0u || len > 256u) {
return -1; /* reject — unreasonable length */
}
/* Generate random bytes using Secure RNG (e.g., hardware TRNG) */
psa_status_t status = psa_generate_random(ns_buf, len);
return (status == PSA_SUCCESS) ? 0 : -2;
}
/**
* secure_hmac_sign() — compute HMAC-SHA256 of a message using the device key.
* The device key never leaves the Secure world.
*/
__attribute__((cmse_nonsecure_entry))
int32_t secure_hmac_sign(const uint8_t *ns_msg, uint32_t msg_len,
uint8_t *ns_mac, uint32_t mac_len)
{
/* Validate both NS pointers */
if (cmse_check_address_range((void *)ns_msg, msg_len, CMSE_NONSECURE) == NULL ||
cmse_check_address_range(ns_mac, mac_len, CMSE_NONSECURE) == NULL) {
return -1;
}
if (mac_len < 32u) return -1; /* HMAC-SHA256 is 32 bytes */
/* Use PSA Crypto HMAC with Secure key */
psa_mac_operation_t op = PSA_MAC_OPERATION_INIT;
psa_key_id_t key_id = 1u; /* pre-provisioned key ID in Secure storage */
psa_status_t s = psa_mac_sign_setup(&op, key_id, PSA_ALG_HMAC(PSA_ALG_SHA_256));
if (s != PSA_SUCCESS) return -2;
s = psa_mac_update(&op, ns_msg, msg_len);
if (s != PSA_SUCCESS) { psa_mac_abort(&op); return -2; }
size_t mac_out_len = 0u;
s = psa_mac_sign_finish(&op, ns_mac, mac_len, &mac_out_len);
return (s == PSA_SUCCESS) ? (int32_t)mac_out_len : -2;
}
Calling Secure Functions from Non-Secure World
/**
* Non-Secure application code — calls Secure APIs via veneer.
* The veneer stubs are in secure_veneer.o (import library).
*/
/* Declarations from the veneer import library (auto-generated header) */
extern int32_t secure_get_random(uint8_t *buf, uint32_t len);
extern int32_t secure_hmac_sign(const uint8_t *msg, uint32_t msg_len,
uint8_t *mac, uint32_t mac_len);
#include <stdint.h>
#include <string.h>
void application_crypto_demo(void)
{
uint8_t nonce[16] = {0};
uint8_t mac[32] = {0};
uint8_t message[] = "Hello, Secure World!";
/* Request random nonce from Secure world */
int32_t rc = secure_get_random(nonce, sizeof(nonce));
if (rc != 0) {
/* Handle error */
return;
}
/* Sign the message — device key stays in Secure world */
rc = secure_hmac_sign(message, strlen((char *)message),
mac, sizeof(mac));
if (rc != 32) {
/* Handle error */
return;
}
/* mac[] now contains HMAC-SHA256 signed with device key */
/* Nonce + MAC can be sent over the network for attestation */
transmit_attestation_token(nonce, sizeof(nonce), mac, sizeof(mac));
}
PSA Security Model
The Platform Security Architecture (PSA) is ARM's framework for building and certifying secure IoT devices. PSA provides a standardised set of APIs — PSA Crypto, PSA IPC, PSA Attestation — and a certification programme (PSA Certified) that gives product managers, procurement teams, and end-users a verifiable security assurance level.
PSA Certified Levels
| Level |
Requirements |
Typical Target |
Example Certified Devices |
| L1 (Questionnaire) |
Self-assessment questionnaire; covers 10 security goals (firmware update, crypto, SRAM scrubbing, etc.) |
Low-cost constrained IoT nodes, industrial sensors |
Nordic nRF9160, NXP LPC55S69, STM32U5 |
| L2 (Lab Tested) |
Independent lab evaluation; software-level penetration testing, source code review; requires TrustZone or equivalent isolation |
Smart home gateways, medical wearables, industrial controllers |
Arm Corstone-300 (M55+Ethos), Infineon PSoC64 |
| L3 (Hardware Tested) |
Physical attack resistance (glitching, side-channel); highest assurance; typically requires dedicated security element or hardened design |
Payment terminals, ePassport readers, critical infrastructure |
Microchip ATECC608, NXP SE050 secure element |
PSA Crypto API — CMSIS Integration
The PSA Crypto API (implemented by Mbed TLS / TF-M) provides hardware-accelerated cryptographic operations callable from Secure code. CMSIS-aware MCUs with hardware crypto accelerators (AES, SHA, PKA/ECC) expose them transparently through the PSA API — you write portable code once and the hardware acceleration is used automatically.
/**
* PSA Crypto API — symmetric encryption example.
* Works on any PSA-compliant platform (TF-M, Mbed TLS, etc.).
*/
#include "psa/crypto.h"
#define KEY_BITS 128u
static void psa_aes_encrypt_example(void)
{
psa_status_t status;
psa_key_attributes_t attrs = PSA_KEY_ATTRIBUTES_INIT;
psa_key_id_t key_id = 0u;
const uint8_t key_data[16] = {
0x00,0x01,0x02,0x03, 0x04,0x05,0x06,0x07,
0x08,0x09,0x0A,0x0B, 0x0C,0x0D,0x0E,0x0F
};
const uint8_t plaintext[16] = "Hello, PSA Crypt";
uint8_t ciphertext[32] = {0};
size_t cipher_len = 0u;
/* 1. Initialise PSA Crypto subsystem */
status = psa_crypto_init();
if (status != PSA_SUCCESS) return;
/* 2. Set key attributes: AES-128 for CBC encryption */
psa_set_key_type(&attrs, PSA_KEY_TYPE_AES);
psa_set_key_bits(&attrs, KEY_BITS);
psa_set_key_usage_flags(&attrs, PSA_KEY_USAGE_ENCRYPT | PSA_KEY_USAGE_DECRYPT);
psa_set_key_algorithm(&attrs, PSA_ALG_CBC_NO_PADDING);
/* 3. Import key into PSA key store */
status = psa_import_key(&attrs, key_data, sizeof(key_data), &key_id);
if (status != PSA_SUCCESS) return;
/* 4. Single-part encrypt */
status = psa_cipher_encrypt(key_id, PSA_ALG_CBC_NO_PADDING,
plaintext, sizeof(plaintext),
ciphertext, sizeof(ciphertext),
&cipher_len);
if (status != PSA_SUCCESS) goto cleanup;
/* ciphertext[0..cipher_len-1] contains the encrypted data */
cleanup:
psa_destroy_key(key_id);
mbedtls_psa_crypto_free();
}
Secure Boot & Code Signing
Secure boot ensures that only authenticated, authorised firmware runs on your device. It is the foundation of the entire security model — if an attacker can replace your firmware with malicious code, every other security measure becomes irrelevant. On ARMv8-M devices, secure boot is implemented in the Secure world (ROM or immutable Secure flash), and it verifies the Non-Secure application image before transferring execution.
Boot Chain Verification
/**
* Minimal secure boot implementation — pseudocode for illustration.
* In practice, use MCUboot or TF-M's secure boot for production.
*
* Boot chain:
* ROM Boot (Secure) → Secure Bootloader (Secure) → NS Application
*
* The Secure Bootloader:
* 1. Verifies its own integrity (optional — may rely on ROM for this)
* 2. Computes SHA-256 of the NS application image
* 3. Verifies ECDSA-P256 signature against the embedded public key
* 4. If valid: transition to Non-Secure world
* 5. If invalid: halt, erase, or enter recovery mode
*/
#include "psa/crypto.h"
#include "cmse_nonsecure_entry.h"
/* Public key — embedded in Secure flash at manufacture (read-only) */
static const uint8_t ota_public_key[65] = {
/* 0x04 || Qx (32 bytes) || Qy (32 bytes) — uncompressed P-256 point */
0x04, /* ... 64 bytes of public key ... */
};
/* Image header structure (simplified MCUboot-compatible) */
typedef struct {
uint32_t ih_magic; /* 0x96F3B83D */
uint32_t ih_load_addr; /* image load address */
uint16_t ih_hdr_size; /* header size in bytes */
uint16_t ih_protect_tlv_size;
uint32_t ih_img_size; /* image body size (bytes) */
uint32_t ih_flags;
uint8_t ih_ver[4]; /* major, minor, revision, build_num */
uint32_t _pad1;
} image_header_t;
#define NS_APP_BASE 0x00040000u /* Non-Secure flash start */
#define NS_APP_SIZE 0x000BFF00u /* max NS app size */
static int32_t verify_ns_image(void)
{
const image_header_t *hdr = (const image_header_t *)NS_APP_BASE;
if (hdr->ih_magic != 0x96F3B83Du) {
return -1; /* no valid image header */
}
/* Compute SHA-256 over the image body */
uint8_t image_hash[32] = {0};
psa_hash_operation_t hash_op = PSA_HASH_OPERATION_INIT;
psa_status_t s = psa_hash_setup(&hash_op, PSA_ALG_SHA_256);
if (s != PSA_SUCCESS) return -2;
const uint8_t *image_body = (const uint8_t *)NS_APP_BASE
+ hdr->ih_hdr_size;
s = psa_hash_update(&hash_op, image_body, hdr->ih_img_size);
if (s != PSA_SUCCESS) { psa_hash_abort(&hash_op); return -2; }
size_t hash_len = 0u;
s = psa_hash_finish(&hash_op, image_hash, sizeof(image_hash), &hash_len);
if (s != PSA_SUCCESS) return -2;
/* Verify ECDSA-P256 signature (stored in image TLV area after body) */
const uint8_t *sig = image_body + hdr->ih_img_size; /* simplified */
uint32_t sig_len = 72u; /* DER-encoded ECDSA-P256 ~ 71-72 bytes */
psa_key_attributes_t key_attrs = PSA_KEY_ATTRIBUTES_INIT;
psa_set_key_type(&key_attrs, PSA_KEY_TYPE_ECC_PUBLIC_KEY(PSA_ECC_FAMILY_SECP_R1));
psa_set_key_bits(&key_attrs, 256u);
psa_set_key_usage_flags(&key_attrs, PSA_KEY_USAGE_VERIFY_HASH);
psa_set_key_algorithm(&key_attrs, PSA_ALG_ECDSA(PSA_ALG_SHA_256));
psa_key_id_t verify_key = 0u;
s = psa_import_key(&key_attrs, ota_public_key, sizeof(ota_public_key),
&verify_key);
if (s != PSA_SUCCESS) return -3;
s = psa_verify_hash(verify_key, PSA_ALG_ECDSA(PSA_ALG_SHA_256),
image_hash, hash_len, sig, sig_len);
psa_destroy_key(verify_key);
return (s == PSA_SUCCESS) ? 0 : -4;
}
Secure-to-Non-Secure World Transition
/**
* Transition from Secure bootloader to Non-Secure application.
* Must clear Secure state before handing over to untrusted NS code.
*/
#include "core_cm33.h"
/* NS application reset handler address — from image header or linker symbol */
#define NS_VECTOR_TABLE 0x00040000u /* NS flash start = vector table */
typedef void (*ns_func_t)(void) __attribute__((cmse_nonsecure_call));
void boot_jump_to_nonsecure(void)
{
/* 1. Set NS Main Stack Pointer limit (optional but recommended) */
__TZ_set_MSPLIM_NS(0x20040000u); /* bottom of NS SRAM */
/* 2. Set NS Vector Table Offset Register */
SCB_NS->VTOR = NS_VECTOR_TABLE;
/* 3. Set NS MSP from the NS vector table (first word = initial SP) */
uint32_t ns_sp = *(volatile uint32_t *)NS_VECTOR_TABLE;
__TZ_set_MSP_NS(ns_sp);
/* 4. Clear any FPU state so NS cannot read Secure FP registers */
__set_CONTROL(0x00u); /* Secure CONTROL: use MSP, privileged, no FPU lazy */
__ISB();
/* 5. Get NS Reset_Handler address (second word of NS vector table) */
uint32_t ns_reset_addr = *(volatile uint32_t *)(NS_VECTOR_TABLE + 4u);
/* Ensure bit 0 is clear — NS function pointer (Thumb but ~NS) */
ns_reset_addr &= ~1u;
/* 6. Branch to NS world via BXNS (Non-Secure Branch and Exchange) */
ns_func_t ns_entry = (ns_func_t)(ns_reset_addr | 1u); /* Thumb bit set */
ns_entry(); /* processor transitions to Non-Secure state */
/* Should never reach here */
for (;;) { __NOP(); }
}
Exercises
Exercise 1
Intermediate
Partition a Simple MCU: Keys in Secure, Application in Non-Secure
Take an LPC55S69, STM32U5, or Nordic nRF5340 development board. Configure the SAU to partition flash and SRAM into Secure and Non-Secure regions. Store a dummy AES-128 key in Secure SRAM. Implement a single veneer function secure_encrypt_block() that accepts a 16-byte plaintext from NS, encrypts it with the Secure key, and returns the ciphertext. Verify that a direct pointer access to Secure SRAM from NS code triggers a SecureFault.
SAU Configuration
Memory Partitioning
Veneer Function
SecureFault
Exercise 2
Intermediate
Secure Random Number API Callable from NS World
Implement a secure_get_random(uint8_t *buf, uint32_t len) veneer function using the hardware TRNG (True Random Number Generator) in the Secure domain. Include proper NS pointer validation using cmse_check_address_range(). From the Non-Secure application, call this function and use the random bytes as a nonce for a message authentication code. Attempt to pass a Secure address as the buffer parameter and confirm the rejection.
TRNG
NS Pointer Validation
PSA Crypto
cmse_check_address_range
Exercise 3
Advanced
Secure Attestation: Signed Device Token
Implement a secure attestation service: (1) store an ECDSA-P256 private key in Secure flash (provisioned at first boot, never readable from NS); (2) implement secure_attest(const uint8_t *ns_challenge, uint32_t len, uint8_t *ns_token, uint32_t *token_len) which constructs a CBOR-encoded attestation token containing the challenge, device firmware version, and a SHA-256 hash of the NS application image; (3) sign the token with the private key using the PSA Crypto API; (4) from the NS application, send the token over UART to a host verifier that checks the signature against the known public key.
PSA Attestation
ECDSA-P256
CBOR
Key Provisioning
TrustZone Partition Planner
Use this tool to document your TrustZone partitioning design — SAU regions, secure/non-secure memory ranges, secure boot chain, and PSA certification target. Download as Word, Excel, PDF, or PPTX for security design review.
Conclusion & Next Steps
TrustZone for Cortex-M is the most powerful security tool available in the ARMv8-M architecture. In this part we covered:
- TrustZone hardware model: Secure, Non-Secure Callable, and Non-Secure security states enforced in hardware — NS code cannot breach the Secure boundary regardless of software vulnerabilities.
- SAU configuration: programming up to 8 regions with base/limit/NSC attributes using CMSIS-Core SAU registers to define the exact memory partitioning.
- Veneer functions: creating
cmse_nonsecure_entry APIs with rigorous parameter validation using cmse_check_address_range() — the gateway pattern that exposes Secure services without exposing Secure internals.
- PSA Crypto API: using standardised, hardware-accelerated cryptographic operations that work portably across PSA-compliant devices.
- Secure boot chain: SHA-256 hash computation and ECDSA-P256 signature verification using PSA Crypto, followed by the correct Secure-to-NS transition sequence including FPU state cleanup.
Next in the Series
In Part 16: Bootloaders & Firmware Updates, we'll build a complete bootloader from scratch — minimal CRC32 verifier with application jump, dual-bank flash swap on STM32H7, UART XMODEM firmware update, MCUboot integration, and fail-safe A/B update strategies that survive a power cut mid-update.
Related Articles in This Series
Part 16: Bootloaders & Firmware Updates
Secure boot is the foundation — Part 16 builds the complete bootloader infrastructure on top, adding OTA transports, dual-bank flash, and MCUboot integration.
Read Article
Part 12: Memory Management in Embedded Systems
Correctly partitioning Secure and Non-Secure SRAM requires understanding memory pool design, linker scripts, and static allocation strategies covered in Part 12.
Read Article
Part 3: Startup Code, Linker Scripts & Vector Table
Both the Secure and Non-Secure images require carefully crafted linker scripts to place code and data in the correct memory regions — the concepts from Part 3 apply directly.
Read Article