Implementing a Portable Cryptographic Testing Framework for Embedded Systems

Embedded Systems
Cryptography
QEMU
STM32
Published

February 14, 2025

Introduction

In embedded cryptographic development, it is often necessary to test cryptographic schemes across different environments, such as real hardware and emulators like QEMU. This requires a flexible approach where tests can be executed in a controlled environment while ensuring the cryptographic implementations remain portable across multiple embedded platforms.

This blog post serves as both an architectural overview and a practical implementation guide for a framework that enables device-independent cryptographic testing using a modular approach.


Solution Overview

The proposed solution consists of:

  • A cryptographic library split into two components:
    • libpqscheme.a: Contains the cryptographic scheme implementation.
    • libpqscheme_test.a: Contains unit tests for the cryptographic scheme.
  • A device-specific test runner (runner) that:
    • Boots the embedded system and sets up the execution environment.
    • Calls the test functions from libpqscheme_test.a.
    • Implements all platform-specific functionality.

The key advantage of this design is that libpqscheme.a and libpqscheme_test.a are compiled only once per architecture, ensuring reusability across multiple platforms. However, runner is compiled separately for each target platform to handle platform-specific operations.


Binary Memory Layout

To support both real hardware and QEMU, the framework follows a standardized memory structure:

Offset Field Description
0x00 Magic Number A unique identifier (0xDEADBEEF) to verify the binary integrity.
0x04 Binary Size The total size of the cryptographic scheme + test suite.
0x08 Entry Point The function address where the test execution starts.

This layout allows runner to verify and execute tests dynamically.


Implementing the Cryptographic Scheme

The cryptographic scheme and its tests must be position-independent (PIC) so that they can be executed from any RAM location.

Linker Script (libpqscheme.ld)

MEMORY
{
    RAM (rwx) : ORIGIN = 0x00000000, LENGTH = 16K  /* Generic RAM location */
}

SECTIONS
{
    .header (NOLOAD) : {
        LONG(0xDEADBEEF)  /* Magic number */
        LONG(SIZEOF(.text))  /* Binary size */
        LONG(ADDR(.text) + 4)  /* Entry point */
    } > RAM

    .text : {
        *(.text)
        *(.rodata)
    } > RAM

    .data : {
        *(.data)
    } > RAM
}

Implementation (libpqscheme_test.c)

#include <stdint.h>

__attribute__((section(".text"))) void cryptographic_test() {
    // Run unit tests for cryptographic scheme
    while(1);  // Debug loop
}

Compilation

arm-none-eabi-gcc -Tlibpqscheme.ld -fPIC -nostartfiles -o libpqscheme.elf libpqscheme.c
arm-none-eabi-ar rcs libpqscheme.a libpqscheme.elf

arm-none-eabi-gcc -Tlibpqscheme.ld -fPIC -nostartfiles -o libpqscheme_test.elf libpqscheme_test.c
arm-none-eabi-ar rcs libpqscheme_test.a libpqscheme_test.elf

Implementing runner

runner is responsible for bootstrapping the MCU and executing cryptographic_test() from libpqscheme_test.a.

Implementation (runner.c)

#include <stdint.h>
#include <string.h>

#define LIB_MAGIC      0xDEADBEEF
#define FLASH_LIB_ADDR 0x08020000  // Example: `libpqscheme_test.a` stored in Flash
#define RAM_LIB_ADDR   0x20010000  // Load location in RAM
#define LIB_MAX_SIZE   (16 * 1024)

typedef void (*func_t)(void);

void copy_lib_to_ram() {
    memcpy((void *)RAM_LIB_ADDR, (void *)FLASH_LIB_ADDR, LIB_MAX_SIZE);
}

void execute_library_function(uint32_t lib_base_addr) {
    uint32_t *header = (uint32_t *)lib_base_addr;
    if (header[0] != LIB_MAGIC) return;  // Validate magic number
    uint32_t entry_point = lib_base_addr + header[2];
    func_t test_func = (func_t)entry_point;
    if (test_func) test_func();
}

void main() {
    copy_lib_to_ram();
    execute_library_function(RAM_LIB_ADDR);
}

Compilation for STM32

arm-none-eabi-gcc -Tstm32.ld -nostartfiles -o runner.elf runner.c
arm-none-eabi-ar rcs runner runner.elf

Running on Different Platforms

Flashing to STM32 Using st-flash

st-flash write runner 0x08000000
st-flash write libpqscheme_test.a 0x08020000

Running on QEMU

qemu-system-arm -M stm32f4discovery -cpu cortex-m4 -nographic -kernel runner.elf -device loader,file=libpqscheme_test.elf,address=0x08020000

Conclusion

By implementing this approach:

  • libpqscheme.a and libpqscheme_test.a are compiled once per architecture.
  • runner is compiled separately for each platform to handle device-specific setup.
  • The framework supports real hardware and QEMU for testing.
  • Debugging is consistent across all platforms using GDB & QEMU.

This modular design ensures maximum portability and testability across embedded cryptographic systems.