Building secure IOT with ARM Cortex M23 µC and TrustZone

A Tutorial on Programming with TrustZone

Daniel Ehnes
C³AI
11 min readJun 1, 2019

--

LINK TO THE GIT REPO

This post aims to

  • show you the fundamentals of the ARM TrustZone functionality
  • give you a tutorial plus repo of how to build your own TrustZone powered AES crypto demo on the ARM Cortex M23 powered Nuvoton M2351 IOT microcontroller board

Introduction

Since the main target of electronics develop from static to transportable, mobile devices, the targets of hackers changed as well. Server and desktop computers aren’t anymore the only target to exploit sensible and valuable information from. Small and mobile devices like smartphones or tablets as well as Internet of Thing (IOT) gadgets are now in the focus of exploits.

The physical limitations of these mostly on low-power hardware running systems directly relate to their possible maximum size and energy consumption. Therefore, the security extensions often are viewed as negligible to the advantage of more performance, a longer battery life or another memory cell.

ARM introduced an extension for ARM architecture devices, which can withstand the previous statements and convinces with a security implementation on hardware level — the ARM TrustZone. TrustZone allows the developer to work with a secure instruction set in a secure world to keep the application safe even on a compromised system.

The tutorial part requires following soft- and hardware to replicate:

The TrustZone High-Level Idea

The high-level idea of TrustZone is the physical isolation of two execution environments to protect the illegal crossing of unauthorized programs into a secured area. A secure world and a non-secure world are defined, in which different instruction sets ensure the valid execution of code and prevent trespassers from tampering or exploiting information from the secure into the non-secure world.

To achieve this goal, the processor must switch its context between the different worlds. A program, without legal access to the secure world, running in the normal world doesn’t even know about the existence of the secure world because of this barrier on the hardware level provided by the CPU.

Software implementation with the idea of isolating worlds from each other suffer from the fact that this barrier is built with a maximum depth of the kernel, which leads to the fact that they are not secure if the kernel is malicious. The fact that software security implementations are at the performance side miserable compare to hardware implementations is another knock-down argument.

The principle behind Trusted Execution Environments (TEE) is the assumption, that the normal world of a system is malicious and therefore cannot be trusted. The trusted world is the so-called trust anchor in this environment, which holds security critical components like Keys, DRM or randomness generators as well as application data stored from trusted apps into the secure world. With this approach remote participants can rely on a trusted environment for operations like authentication or key exchange.

ARMv8 architectures -M & -A

The Cortex-M architecture focuses on real time capability and efficient interrupt handling. Therefore it doesn’t implement a secure monitor at software level, which takes care of mode switching with higher latency. The Cortex-M implements the functionality of secure and non-secure world distinction at the hardware level, to ensure time efficient world switches without delays, to achieve real time compatibility. It utilizes source and origin memory addresses to check the security conditions. This characteristic behaviour makes it valuable for keeping the firmware and updates as well as the GPIO’s or the boot process as secure as possible.

Since the Cortex-A architecture is used in more performant and in generall bigger systems, it implements a secure monitor, which handles the state switching. This functionality is achieved by dividing a physical processor core into two virtual cores — one operating in the secure, and the other one operating in the non-secure world. This distinction separates the two worlds to achieve the TrustZone security.

ARM TrustZone for ARMv8-M (Cortex-M)

Startup

On reset the processor is in secure mode and the SAU is disabled. All hardware peripherals are attributed to the secure mode.

Memory Management

On ARMv8-M processors with enabled Security Extension the memory space is divided in two types of memory: secure and non-secure memory. Secure memory can only be accessed from the Secure World, while non-secure memory can be accessed by both worlds.

Within in the secure memory there exists an additional type of memory called the non-secure callable memory. This type of memory is used to switch from non-secure mode to secure mode. The instruction SG (Secure Gateway) to switch to secure mode is only allowed to occur in non-secure callable memory.

Memory regions and their security state are defined either by a Secure Attribution Unit (SAU) or an implementation Defined Attribution Unit (IDAU). The SAU allows to adjust the memory layout at runtime. The IDAU is defined by the hardware manufacturer and enables the definition of upto 255 memory regions. Regions defined by multiple units take the highest attribution. Therefore if an address is defined to be non-secure by the SAU and secure by the IDAU, the address is considered to be secure. To note secure is considered higher than non-secure callable.

The SAU allows a limited amount of regions to be defined (up to 8). It is controlled through a special set of registers. These registers allow to enable and disable the SAU and to set the start and end addresses of memory regions, as well as their security type. Through the SAU only non-secure or non-secure callable memory regions can be defined, the remaining memory is defined as secure.

When a memory address is accessed, the processor checks the SAU and the IDAU, to determine the security of the address. If the processor is in non-secure mode and a secure address is accessed a hard fault is generated.

State-Switching

There exist three instructions which allow to switch to different security state: SG, BXNS, BLXNS. The security state of the processor is determined by the address currently executed. If the address lies within the secure memory, the mode is secure otherwise the processor is in non-secure mode.

The SG instruction allows to switch from non-secure to secure mode. It must be located within an non-secure callable memory region and be the first one executed, when branching into that region. Through this instruction secure API calls can be implemented.

BXNS is used to return to non-secure mode after calling a secure function from non-secure mode.

The instruction BLXNS allows the secure world to call a function in or branch into the normal world.

When transitioning from secure to non-secure mode, the return address and processor state are pushed onto the stack of the secure mode. The link register of the processor is set to the value FNC_RETURN. To return to secure mode, the non-secure mode has to branch to FNC_RETURN and then the values saved on the stack including the true return address will be restored.

Interrupts and exceptions can be configured during secure mode to be secure or non-secure. They then have to be handled by the configured security mode.

If an interrupt or exception occurs during execution in secure mode that has to be handled in non-secure mode, the processor saves all secure information like registers on the secure stack and erases them. Then the switch to non-secure mode is done.

Nuvoton M2351

The M2351 board contains a Security Configuration Unit (SCU). The SCU is used to configure the security attribution for hardware peripherals like SRAM or GPIOs. It is also responsible for generating an interrupt when security violations occur.

The IDAU on the M2351 uses the 28th bit of an address to distinguish between secure and non-secure addresses. Addresses with the 28th bit set to 0 are considered non-secure, with 1 as secure. Addresses above 0xE0000000 are exempted, this partitions the memory into 16 regions.

M2351 IDAU memory mapping from NuMicro® Family M2351 Series Datasheet

Against which attackers protects ARMs TrustZone?

The TrustZone protects code and data of the secure world from attackers, who gained code execution within the normal world. An attacker can achieve this by exploiting for example a software vulnerability like a buffer overflow.

An attacker with code execution in the normal world is able to read and write any data stored in memory of the normal world. In addition he can manipulate the control flow of programs executing in the normal world, for example by changing pointers or jumping to arbitrary code blocks of the program.

However any code or data located within the secure world is protected against the attacker, as he cannot access memory of the secure world from the normal world and only special defined entry points in the secure world are allowed to be used.

Therefore TrustZone provides integrity and confidentiality for code and data in the secure world.

TrustZone does not provide security for data send over the network, for example using WIFI or Ethernet. An attacker with the ability to intercept the network traffic of the device, would be able to read and manipulate any data transmitted. He can achieve this by using man-in-the-middle attacks like fake access points or ARP spoofing. The application itself is therefore responsible to protect its network traffic by employing encryption and integrity checking, for example with TLS.

“Hello World Cortex-M”

We want to show in our example “Hello World Cortex-M” how to setup an example project for the Nuvoton M2351 development board which make use of both worlds, secure and normal world.

We will show in our demo the following techniques:

  • use of GPIO (General Purpose I/O) ports in normal word.
  • generate an AES 128 bit key for decrypt and encrypt a “secret” value.
  • initialise UART in both world, for reading from stdin and showing some debug informations.
  • all actions are handled and evaluated through interrupts.

The following things are needed to start developing:

TrustZone Programming on Nuvoton M2351

The sample code CSSD_LED from the Board Support Package (BSP) should be used as base. The code is located in “SampleCode/TrustZone/CSSD_LED”. Be aware when making a copy of the project, as the code depends on other components of the BSP, the project folder cannot be moved to a different location.

The sample code includes two projects “Secure” (secure world part) and “NonSecure” (normal world part). “main.c” in the “Secure” project contains the application logic for the secure world, while “partition_M2351.h” holds the security attribution settings. In the “NonSecure” project the normal world application logic is implemented in “main_ns.c” and the “cssd_lib.h” contains the function prototypes for the secure functions called from the normal world.

Secure function called from the normal world have to be preceded with the “__NONSECURE_ENTRY” macro defined in “main.c”. For access in the normal world project, they have to declared with the keyword “extern” and the function prototype (for example in “cssd_lib.h”.

Switching from the secure world to the normal world works by branching to an address located within in the normal world and clearing the LSB of the address. Therefore all addresses called from the secure world need to have their LSB set to 0. The function “cmse_nsfptr_create” can be used for the purpose, however it needs to be executed at runtime. Therefore a callback API needs to be implemented in the secure world, which will receive the function pointers of the normal world addresses and save them with the LSBs set to 0.

Access to GPIO pins, interrupts and UART ports is restricted for the normal world. For use in the normal world these have to be enabled in “partition_m2351.h” header file. When using the µVision IDE, a configuration wizard allows to adjust access to peripherals, interrupts and memory regions via a graphical user interface. All peripheral components like GPIO pins or UART ports and their corresponding interrupts needed in the normal world, have to be set to “non-secure” in “partition_M2351.h”.

Demo

Okay, now the exciting part, let’s dive into our demo.

Before you can write secure code, you first have to define a partition. With these partition you can set the secure and non secure regions for the M23 processor e.g. which interrupts or IO Ports should be accessible from the non secure program. You can use the µVision Configuration Wizard for this.

We define our “Secure API” (functions that are callable from the normal world) in the beginning of our main file. The API consists of four functions “S_get_secure_value”, “S_get_cipher_value”, “S_encrypt_secure_value” and “S_decrypt_cipher_value”.

We use “S_get_secure_value” to load the “secret” value from stdin into the secure world. This is, of course only for the sake of simplicity. A real application would retrieve a secret e.g. from a GPIO Port that are accessible secure only.

After receiving the secret value, the encryption process is start with “S_encrypt_secure_value”. The cipher text is saved in an internal variable and printed to stdout for other uses. The function “encrypt” executes the encryption.

“S_get_cipher_value” reads the cipher text from stdin and saves it to the corresponding variable. The ciphertext is expected in hexadecimal format. The function “hex_to_byte” is used to convert the input string into bytes.

“S_decrypt_cipher_value” decrypts the ciphertext entered previously. The decrypted secret value is printed to stdout and saved in an internal variable. The decryption is done by the function “decrypt”.

“init_system”: Initialise all necessary registers for system clock and interrupt registers.

“init_debug_port”: Set UART accessible to both worlds for debugging purposes.

“init_crypto”: Initialise the True Random Number Generator (TRNG) which uses physical behaviors to provide strong randomness, further we are using it to generate a 128 bit AES key.

When the initialization in the secure world is finished, the execution is switched to the normal world. This is achieved through the function “init_non_secure”. The entry address in the normal world is read from the vector table of the normal world, the second entry points to the reset handler (entry point for the normal world).

The function “cmse_nsfptr_create” is used to clear the LSB of the address, which will indicate to the processor a switch to the normal world. Then it is checked, whether the address is located within the normal world and if that is the case, the address is called. From this point on the application is running in the normal world.

We kept the normal world program very small, we initialise the IO Port B bit 0 and 1 on the board as inputs and enable interrupts for a rising edge on these two pins. On the Nuvoton M2351, on these pins are two push buttons.

The interrupt handler for Port B (GPB_IRQHandler) triggers the function display_menue to select multiple secure functions (labeled with a “S_” which are executed within the secure world. All secure functions which are callable from the normal world are defined in the header “cssd_lib.h”.

To implement the AES encryption the standard cryptographic driver of the board is used. AES encryption is done with a 128 bit key and in ECB mode. To use the cryptographic driver first the interrupt “CRPT” has to be enabled inside the Nested Vector Interrupt Controller with “NVIC_EnableIRQ(CRPT_IRQn)”. Then AES mode is enabled: “AES_ENABLE_INT(CRPT)”.

The key is generated using the True Random Number Generator (TRNG) of the board, which outputs up to 500 random bits per second from an entropy source. It is first initialized “BL_RandomInit(&rng, BL_RNG_PRNG | BL_RNG_LIRC32K)” and then the key is generated by “BL_Random(&rng, aes_key, nbits / 8)”.

The encryption and decryption operation are implemented in the functions “encrypt” and “decrypt”. Both operations work similar.

First with “AES_Open(CRPT, 0, 1, AES_MODE_ECB, AES_KEY_SIZE_128, AES_IN_OUT_SWAP)” the parameters for the cryptographic operation are defined, like the key size, the mode or the type of operation. For encryption the third parameter is “1”, for decryption the third parameter has to be “0”.

“AES_SetKey(CRPT, 0, (uint32_t*)aes_key, AES_KEY_SIZE_128)” sets the key and “AES_SetInitVect(CRPT, 0, aes_iv)” the initial vector.

With “AES_SetDMATransfer(CRPT, 0, (uint32_t)secret_value, (uint32_t)cipher_value, strlen((char *)secret_value))” the data to operated on and the buffer for the result are transmitted. The third parameter represents the input data, with the 5th parameter being its size and the fourth parameter is the buffer for the result.

“AES_Start(CRPT, 0, CRYPTO_DMA_ONE_SHOT)” starts the cryptographic operation, when finished, the “CRPT” interrupt is raised.

Acknowledgement

The tutorial including the demo programming is joint work with Sascha Greve and Daniel Eggelmann

Sources

TrustZone technology for the ARMv8-M architecture

NuMicro® Family M2351 Series Datasheet

--

--