Implementing a Portable Cryptographic Testing Framework for Embedded Systems
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>
((section(".text"))) void cryptographic_test() {
__attribute__// 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() {
((void *)RAM_LIB_ADDR, (void *)FLASH_LIB_ADDR, LIB_MAX_SIZE);
memcpy}
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)entry_point;
func_t test_func if (test_func) test_func();
}
void main() {
();
copy_lib_to_ram(RAM_LIB_ADDR);
execute_library_function}
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
andlibpqscheme_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.