The "Holy Bible" for embedded engineers
Building Unbreakable Trust from Power-On to Runtime
Understanding how secure boot creates a foundation of trust that protects your entire system
Imagine you’re building a house. You wouldn’t start with the walls or roof—you’d start with a solid foundation. Secure boot is that foundation for your embedded system’s security.
The Trust Problem
When your device powers on, it’s in a completely untrusted state. Anyone could have:
The Solution: Secure Boot
Secure boot ensures that only trusted, verified code can run on your system. It’s like having a security guard that checks everyone’s ID before they enter a building.
A chain of trust works like a relay race where each runner must verify the next runner before passing the baton. If any link in the chain is broken, trust is lost.
Power-On → ROM → Bootloader → Kernel → Applications
↓ ↓ ↓ ↓ ↓
Untrusted → Trusted → Trusted → Trusted → Trusted
State Code Code Code Code
How It Works:
┌─────────────────────────────────────┐
│ ROM (Trusted) │
│ ┌─────────────────────────────┐ │
│ │ Public Key Hash │ │
│ │ (Hardcoded in ROM) │ │
│ └─────────────────────────────┘ │
└─────────────────────────────────────┘
↓
Verify Bootloader
↓
┌─────────────────────────────────────┐
│ Bootloader (Verified) │
│ ┌─────────────────────────────┐ │
│ │ Digital Signature │ │
│ │ (Signed by trusted key) │ │
│ └─────────────────────────────┘ │
└─────────────────────────────────────┘
↓
Verify Kernel
↓
┌─────────────────────────────────────┐
│ Kernel (Verified) │
│ ┌─────────────────────────────┐ │
│ │ Digital Signature │ │
│ │ (Signed by trusted key) │ │
│ └─────────────────────────────┘ │
└─────────────────────────────────────┘
When power is applied, the system starts in a completely untrusted state:
// Hardware reset sequence
void hardware_reset() {
// Clear all registers and memory
clear_all_registers();
clear_all_memory();
// Disable all peripherals except essential ones
disable_non_essential_peripherals();
// Set security mode to highest level
set_security_mode(SECURITY_MODE_HIGHEST);
// Jump to ROM code
jump_to_rom();
}
The ROM contains the first trusted code that can’t be modified:
// ROM code (immutable, trusted)
void rom_entry_point() {
// Initialize minimal hardware
init_minimal_hardware();
// Load bootloader from storage
uint8_t* bootloader = load_bootloader_from_storage();
// Verify bootloader signature
if (!verify_bootloader_signature(bootloader)) {
// Bootloader verification failed
enter_secure_failure_mode();
return;
}
// Calculate bootloader hash
uint8_t calculated_hash[32];
calculate_hash(bootloader, BOOTLOADER_SIZE, calculated_hash);
// Compare with trusted hash
if (!compare_hashes(calculated_hash, TRUSTED_BOOTLOADER_HASH)) {
// Hash mismatch - bootloader corrupted
enter_secure_failure_mode();
return;
}
// Bootloader is trusted - execute it
execute_bootloader(bootloader);
}
The bootloader must verify itself and the next component:
// Bootloader verification
typedef struct {
uint8_t signature[64]; // Digital signature
uint8_t hash[32]; // Code hash
uint32_t version; // Version number
uint32_t flags; // Security flags
uint8_t reserved[16]; // Reserved for future use
} BootloaderHeader;
bool verify_bootloader_integrity(uint8_t* bootloader) {
BootloaderHeader* header = (BootloaderHeader*)bootloader;
// Step 1: Verify signature using ROM public key
uint8_t* code_start = bootloader + sizeof(BootloaderHeader);
uint32_t code_size = BOOTLOADER_SIZE - sizeof(BootloaderHeader);
if (!verify_signature(header->signature,
code_start,
code_size,
ROM_PUBLIC_KEY)) {
return false; // Signature verification failed
}
// Step 2: Verify code hash
uint8_t calculated_hash[32];
calculate_hash(code_start, code_size, calculated_hash);
if (memcmp(calculated_hash, header->hash, 32) != 0) {
return false; // Hash mismatch
}
// Step 3: Check version compatibility
if (header->version < MIN_SUPPORTED_VERSION) {
return false; // Version too old
}
// Step 4: Check security flags
if (header->flags & FLAG_DEBUG_ENABLED) {
return false; // Debug mode not allowed
}
return true; // All checks passed
}
The bootloader verifies the kernel before loading it:
// Kernel verification process
bool verify_kernel(uint8_t* kernel_image) {
KernelHeader* header = (KernelHeader*)kernel_image;
// Verify kernel signature
if (!verify_signature(header->signature,
kernel_image + sizeof(KernelHeader),
header->code_size,
BOOTLOADER_PUBLIC_KEY)) {
return false;
}
// Verify kernel hash
uint8_t calculated_hash[32];
calculate_hash(kernel_image + sizeof(KernelHeader),
header->code_size,
calculated_hash);
if (memcmp(calculated_hash, header->hash, 32) != 0) {
return false;
}
// Check kernel security attributes
if (!verify_kernel_security_attributes(header)) {
return false;
}
return true;
}
You’ll need several cryptographic functions:
// Cryptographic function prototypes
typedef struct {
uint8_t modulus[256]; // RSA modulus
uint8_t exponent[4]; // RSA exponent
} RSAPublicKey;
// Hash calculation (SHA-256)
void calculate_sha256(const uint8_t* data,
uint32_t length,
uint8_t* hash);
// RSA signature verification
bool verify_rsa_signature(const uint8_t* signature,
const uint8_t* data,
uint32_t data_length,
const RSAPublicKey* public_key);
// HMAC calculation for integrity
void calculate_hmac(const uint8_t* key,
uint32_t key_length,
const uint8_t* data,
uint32_t data_length,
uint8_t* hmac);
Managing cryptographic keys is critical:
// Key storage structure
typedef struct {
uint8_t key_id[16]; // Unique key identifier
uint8_t public_key[256]; // Public key data
uint32_t key_type; // Key type (RSA, ECC, etc.)
uint32_t key_size; // Key size in bits
uint32_t permissions; // What this key can verify
uint8_t reserved[32]; // Reserved for future use
} TrustedKey;
// Key database
typedef struct {
uint32_t num_keys; // Number of trusted keys
TrustedKey keys[MAX_KEYS]; // Array of trusted keys
uint8_t reserved[64]; // Reserved for future use
} KeyDatabase;
// Find key by ID
TrustedKey* find_trusted_key(const uint8_t* key_id) {
for (int i = 0; i < key_database.num_keys; i++) {
if (memcmp(key_database.keys[i].key_id, key_id, 16) == 0) {
return &key_database.keys[i];
}
}
return NULL; // Key not found
}
Protect keys and sensitive data:
// Secure storage implementation
typedef struct {
uint8_t data[256]; // Encrypted data
uint8_t iv[16]; // Initialization vector
uint8_t tag[16]; // Authentication tag
} EncryptedData;
// Encrypt sensitive data
bool encrypt_secure_data(const uint8_t* plaintext,
uint32_t length,
const uint8_t* key,
EncryptedData* encrypted) {
// Generate random IV
if (!generate_random_bytes(encrypted->iv, 16)) {
return false;
}
// Encrypt data using AES-GCM
if (!aes_gcm_encrypt(plaintext, length,
key, encrypted->iv,
encrypted->data, encrypted->tag)) {
return false;
}
return true;
}
// Decrypt sensitive data
bool decrypt_secure_data(const EncryptedData* encrypted,
const uint8_t* key,
uint8_t* plaintext,
uint32_t* length) {
// Decrypt using AES-GCM
if (!aes_gcm_decrypt(encrypted->data, encrypted->tag,
key, encrypted->iv,
plaintext, length)) {
return false;
}
return true;
}
Prevent attackers from downgrading to vulnerable versions:
// Version management
typedef struct {
uint32_t major_version; // Major version number
uint32_t minor_version; // Minor version number
uint32_t patch_version; // Patch version number
uint32_t build_number; // Build number
} VersionInfo;
// Check for rollback attempts
bool check_version_rollback(const VersionInfo* new_version) {
VersionInfo current_version = get_current_version();
// Compare version numbers
if (new_version->major_version < current_version.major_version) {
return false; // Major version rollback
}
if (new_version->major_version == current_version.major_version) {
if (new_version->minor_version < current_version.minor_version) {
return false; // Minor version rollback
}
if (new_version->minor_version == current_version.minor_version) {
if (new_version->patch_version < current_version.patch_version) {
return false; // Patch version rollback
}
}
}
return true; // No rollback detected
}
Ensure updates maintain security:
// Update verification
typedef struct {
uint8_t signature[64]; // Update signature
uint8_t hash[32]; // Update hash
VersionInfo version; // Update version
uint32_t update_size; // Size of update
uint8_t update_type; // Type of update
uint8_t reserved[15]; // Reserved
} UpdateHeader;
bool verify_update_package(const uint8_t* update_data) {
UpdateHeader* header = (UpdateHeader*)update_data;
// Verify update signature
if (!verify_signature(header->signature,
update_data + sizeof(UpdateHeader),
header->update_size,
UPDATE_SIGNING_KEY)) {
return false;
}
// Check for rollback
if (!check_version_rollback(&header->version)) {
return false;
}
// Verify update hash
uint8_t calculated_hash[32];
calculate_hash(update_data + sizeof(UpdateHeader),
header->update_size,
calculated_hash);
if (memcmp(calculated_hash, header->hash, 32) != 0) {
return false;
}
return true;
}
Provide recovery options while maintaining security:
// Recovery mode handling
typedef enum {
RECOVERY_MODE_NONE,
RECOVERY_MODE_UPDATE,
RECOVERY_MODE_FACTORY_RESET,
RECOVERY_MODE_DEBUG
} RecoveryMode;
bool enter_recovery_mode(RecoveryMode mode) {
// Check if recovery mode is allowed
if (!is_recovery_mode_allowed(mode)) {
return false;
}
// Verify recovery mode request
if (!verify_recovery_request(mode)) {
return false;
}
// Set recovery mode
set_recovery_mode(mode);
// Reboot into recovery mode
system_reboot();
return true;
}
Next Topic: TPM 2.0 Basics → Cryptographic Foundations