Firmware Security Architecture: Secure Boot, Firmware Integrity and Key Management

Firmware Security Architecture: Secure Boot, Firmware Integrity and Key Management

Article 07 assumed failures were accidental. Rails dip, peripherals glitch, bits flip, and the architecture responds. Secure boot starts from the opposite assumption.

An attacker is a different kind of fault source. They read your recovery documentation and pick the path that helps them. Your bounded retry limit becomes a denial of service. Your diagnostic detail becomes an oracle. Your recovery mode becomes the way in, because it’s the smallest firmware with the fewest checks and it was written in a hurry.

So the question changes from “what happens when this fails” to “what happens when someone makes this fail, deliberately, repeatedly, with the device on their bench.”

This article covers what makes firmware trustworthy: the root of trust, what secure boot actually verifies and what it costs in milliseconds, key hierarchy and rotation across a ten-year life, anti-rollback and the physical budget it consumes, and debug lock decisions that can’t be undone. Article 12 covers getting a new image onto the device safely — different problem, and the two get conflated constantly.


The Attack Surface You Actually Have

Threat modelling in the abstract produces generic advice. Here’s the KIC-400’s real surface, which is more instructive:

InterfaceReachable byWhat it gets themRealistic control
Modbus TCP :502Anyone on the plant networkRead and write registersNone at the protocol level. Segment the network.
Modbus RTU (RS-485)Physical access to the busSame, plus bus disruptionNone. Physical security.
MQTT over TLSNetwork path to the brokerTelemetry, remote commandsMutual TLS, per-device identity
Update endpointNetworkCode execution, persistenceSignature verification, anti-rollback
SWD headerPhysical accessFirmware readout, key extractionRDP level, and it’s a hard choice
External EEPROMPhysical access, I2C probeConfiguration, any stored secretDon’t store secrets there

The first two rows are the honest part, and most security write-ups skip it: Modbus has no security. Not weak security — none. No authentication, no integrity, no confidentiality, in either RTU or TCP form. Anyone who can reach port 502 can write any holding register.

You cannot fix that in firmware. What you can do is state it plainly in your documentation, refuse to let Modbus writes reach anything safety-relevant without a second channel, and design assuming the fieldbus is hostile. A product that claims Modbus TCP is “secured” because the device does secure boot has misunderstood what secure boot is for.

That distinction matters architecturally. Secure boot protects what executes. It does nothing about what a running, authentic image accepts.

KIC-400 attack surface: Modbus TCP and RTU with no protocol security, external EEPROM, MQTT over TLS, update endpoint and SWD header, each with its realistic control.
Six interfaces, and the control that realistically applies to each.

Root of Trust: What “Immutable” Actually Means

Every chain of trust terminates somewhere an attacker can’t change. If it terminates somewhere they can, the whole structure is decorative.

On an STM32H7, the candidates and what each is worth:

Option bytes with the security bit set. The MCU boots into a defined secure area rather than the reset vector, and that area is protected from erase. Changing it requires an unlock sequence that triggers a mass erase.

Write-protected flash sectors (WRP). The bootloader’s sectors can’t be written or erased while protection is active.

PCROP (proprietary code readout protection). Marks a region execute-only — readable by the CPU as instructions, not as data. Useful for keeping key material or verification code from being dumped by a firmware image that’s authentic but compromised.

OTP. One-time-programmable storage, where the part has it. Bits go from 1 to 0 and never back, which makes it the natural home for a monotonic security counter, the subject of the anti-rollback section below. Check your exact part: some H7 variants, such as the H7A3/H7B3, have an OTP area, but the H743 in the KIC-400 does not. On the H743 the counter needs another home, such as the secure element below or a flash sector that only the protected bootloader can write, used append-only.

A separate secure element. An external chip that holds keys and performs operations without ever exposing the private material. Adds a BOM line and an I2C bus, and buys you key storage the MCU genuinely cannot leak.

The architectural question is which of these your part supports and which you’ve actually enabled, and those are frequently different answers. A product with a beautiful signature verification routine, executing from flash that’s readable over SWD because RDP was never raised, has a root of trust an attacker can edit.

Chain of trust from hardware root of trust to immutable bootloader to signed application, with runtime inputs outside what secure boot covers.
Each stage is verified by the one before, down to something immutable.

One thing to be clear about, because the assumption is widespread: Cortex-M7 has no TrustZone. TrustZone-M is a Cortex-M33 feature — STM32L5, U5, H5. On an H7 your isolation primitives are the MPU and the privileged/unprivileged split, and they’re weaker. Plan accordingly rather than discovering it during a security review.


Integrity, Authenticity, and Why CRC Isn’t Either

Briefly, because this is table stakes and the series has already used CRCs for their proper purpose.

A CRC answers “did this change by accident.” It’s excellent at that, cheap, and article 02 uses it on configuration for exactly that reason.

It answers nothing about deliberate change, because CRC32 is trivially invertible — modify the image, compute the new CRC, write it in. A hash answers “did this change,” including deliberately, but only if the stored hash is itself protected. A signature answers “did this come from someone holding the private key,” which is the question that matters, and it’s the only one of the three that survives an attacker who can rewrite the whole flash image including its metadata.

So: CRC for accidental corruption, signature for authenticity, and never substitute one for the other.

Comparison of CRC32, SHA-256 hash and digital signature: what question each answers and whether it survives a deliberate attacker.
CRC for accidents, signature for authenticity.

What Secure Boot Actually Verifies

A signature check is the headline, but a bootloader that only checks a signature will happily install a correctly signed image built for a different board revision.

The full check list, in the order you want it, because each cheap check that fails saves you an expensive one:

1. Header magic and structure valid          ~free
2. Image size within slot bounds             ~free
3. Hardware revision compatible              ~free
4. Security version ≥ stored minimum         ~free  ← anti-rollback
5. Vector table points inside the slot       ~free
6. SHA-256 over the image                    expensive
7. ECDSA verify of the signature over hash   moderate

Check 5 is worth calling out. An image whose reset vector points outside its own slot is either corrupt or hostile, and checking it costs two comparisons. Skip it and a partially-written image can transfer control somewhere arbitrary before anything else notices.

And what it costs

Nobody puts numbers on this, and it’s a boot-time budget item.

Hashing dominates. Software SHA-256 on a Cortex-M runs in the region of 25–40 cycles per byte, so a 1 MB image at 480 MHz is roughly:

1,048,576 bytes × ~30 cycles = ~31 M cycles ÷ 480 MHz ≈ 65 ms

Parts in the family with a hardware HASH peripheral, such as the H753, cut that substantially, because hashing becomes bound by bus throughput rather than the core. The H743 in the KIC-400 has no HASH or CRYP peripheral, so it pays the software figure. ECDSA P-256 verification is a few million cycles in software, so call it 5–20 ms depending on your library and curve optimisations. Ed25519 verification is typically faster than P-256 if you’re free to choose.

So secure boot on this product costs somewhere around 70 to 85 ms, and closer to 15 ms on a part with hardware hashing. Measure yours — these are order-of-magnitude figures to size a budget, not numbers to quote.

That has a real architectural consequence. If your product has a boot-time requirement — an industrial controller that must resume control within a defined window after a power blip — 85 ms of hashing is a line item you have to plan for, and discovering it late means either dropping verification or missing the requirement.

Seven secure boot checks ordered from free to expensive, with a boot-time budget of roughly 70 to 85 ms in software, as on the H743, versus about 15 ms on parts with a hardware HASH peripheral.
Cheap checks first; hashing dominates the boot-time cost.

The tempting shortcut, and its cost. Verify on update, store a “verified” flag, and skip the hash on every subsequent boot. Boot time drops to a few milliseconds. What you’ve done is move your trust from the signature to the flag — so now the flag has to live somewhere an attacker can’t write, which usually means OTP or a protected sector, and you’ve traded a cryptographic guarantee for a storage-protection one. Defensible on a part with strong write protection and a fast boot requirement. Indefensible if the flag sits in ordinary flash.


Key Hierarchy, and Rotation Across Ten Years

The most common key mistake isn’t weak crypto. It’s one key doing four jobs.

KeyPurposeWhere the private half livesRotatable?
Firmware signingProves an image is yoursHSM, offline, in your buildingHard — see below
Device identityProves this unit is yoursOn the device, ideally never exportedPer-device, via re-provisioning
TLS clientAuthenticates to the brokerDerived from or same as identityYes, with a provisioning path
Storage encryptionProtects data at restDerived from a device-unique secretYes, with re-encryption

Separation limits blast radius. A compromised storage key exposes one device’s configuration. A compromised signing key means anyone can produce firmware your entire fleet accepts, which is an unrecoverable event for products that have already shipped — and that asymmetry is why signing keys live in an HSM and never on a build machine.

Four keys and their jobs: firmware signing in an offline HSM, device identity, TLS client and storage encryption, with the blast radius of each compromise.
One key per job limits the blast radius.

The rotation problem nobody plans for

Here’s the ten-year question. Your signing key is compromised in year six. What now?

If the public verification key is burned into OTP or baked into an immutable bootloader, you cannot rotate it. Every fielded device will verify against a key an attacker now controls, for the rest of the product’s life, and the only fix is physical access to every unit.

Three designs that keep the option open, in ascending cost:

Multiple key slots. Burn two or three public keys at provisioning, with a revocation bitmap in OTP. Compromise slot 0, mark it revoked, sign with slot 1. Simple, bounded — you get as many rotations as you provisioned slots, and no more.

A signed key-update path. The bootloader accepts a key-update package signed by the current key. Elegant, and it has a bootstrap problem: if the current key is compromised, the attacker can also update keys. Only useful for planned rotation, not for compromise recovery.

A separate, offline root. A root key signs intermediate signing keys with limited validity. The root is used a handful of times in its life and lives in a safe. This is what a real PKI looks like, and it’s proportionate once your fleet is large enough that a signing key compromise is a company-ending event rather than a bad quarter.

Pick one at architecture time. Retrofitting key rotation onto a fielded fleet is not an engineering task, it’s a recall.

Three key rotation designs in ascending cost: multiple key slots, a signed key-update path, and an offline root with intermediate keys.
Decide how keys rotate before the fleet ships.

Anti-Rollback, and the Budget It Spends

An attacker installs firmware 3.0 — correctly signed, genuinely yours, and containing the vulnerability you fixed in 5.0. Signature verification passes, because the signature is valid. It is your firmware.

The defence is a security version separate from the marketing version, stored monotonically, checked at boot.

Separate from the firmware version, and that distinction matters. Firmware version goes 5.0.1, 5.0.2, 5.1.0 — it moves for every release. Security version moves only when a release fixes something that must never be downgraded past. Most releases don’t bump it. A product might ship forty firmware versions and reach security version 6.

That’s not fastidiousness, it’s arithmetic. The security version has to be stored somewhere an attacker can’t decrement, which means OTP, or on a part without it a secure element’s monotonic counter, and that storage is one-way and finite. Spend one bit per increment — a thermometer code, where version n means n bits burned — and a 1 KB OTP area gives you thousands of increments, though you won’t be dedicating all of it. Bump the security version on every release and you’ll eventually run out; bump it only on security-relevant releases and you never will.

The check is trivial once the storage decision is right:

if (image_hdr.security_version < otp_read_security_version()) {
    fault_record(SEC_ROLLBACK_ATTEMPT, image_hdr.security_version);
    return BOOT_REJECT;
}

And the ordering rule: burn the new security version only after the new image has proven itself. Burn it at install time and a failed update leaves you unable to roll back to the working image, because the working image is now below the minimum. That’s a self-inflicted brick, and it’s the tightest coupling point between this article and article 12’s boot confirmation.

Illustrative release history where the firmware version changes every release but the security version only on security fixes, stored as a one-way thermometer code, burned only after the new image proves itself.
A separate, monotonic security version, burned after boot confirmation.

Provisioning: The Factory Is a Trust Boundary

Every device needs a unique identity. How it gets one determines whether your private keys ever exist somewhere they can be stolen in bulk.

Injected keys. A provisioning station generates a keypair and writes the private key to the device. Simple, works with any part. It also means every private key in your fleet existed, at some point, on a PC in a factory — possibly one you don’t own, in a country whose legal system you haven’t thought about. Compromise that station and you have the whole production run.

On-device generation. The device generates its own keypair using its hardware TRNG, exports only the public key, and the factory station forwards that to a CA for signing. The private key never leaves the part and never existed anywhere else.

On-device generation is strictly better and the H7 has the TRNG for it. The costs are real but modest: a few hundred milliseconds per unit at test, a provisioning protocol rather than a flash write, and a CA the factory can reach. For anything with a meaningful fleet size, it’s the right answer.

Provisioning comparison: injected keys generated on a factory PC versus on-device key generation where only the public key leaves the part.
On-device key generation keeps private keys off the factory floor.

Either way, decide what happens to the provisioning capability afterwards. A device that will re-provision on request has a permanent identity-replacement interface, which is either a service feature or a vulnerability depending entirely on how it’s authenticated.


Debug Lock: The Decision You Can’t Undo

STM32 readout protection has three levels, and the choice between the last two is one of the more consequential irreversible decisions in a product.

Level 0. No protection. Full SWD access, flash readable. Development only.

Level 1. Flash is inaccessible to the debugger. Reverting to Level 0 triggers a mass erase — the chip is usable again, the firmware and any stored secrets are gone. Reversible at the cost of everything on the part.

Level 2. Debug permanently disabled. The JTAG/SWD port is dead. There is no unlock sequence, no ST recovery procedure, no regression path. Permanent.

Most products should ship at Level 1, and I’d defend that against the instinct to choose maximum protection.

Level 1 already prevents firmware and key extraction over SWD — the mass-erase-on-regression means an attacker who reverts to Level 0 gets a blank chip. What Level 2 additionally buys is protection against attacks that work despite Level 1, which is a narrower set than it sounds.

What Level 2 costs is every failure analysis you’ll ever want to do. A unit comes back from the field with an unexplained fault. At Level 1 you can, with the right procedure, still work with it. At Level 2 you have a brick with an interesting story and no way to hear it. Over a ten-year product life with field returns, that’s a real and recurring cost.

Level 2 is the right call when physical compromise of a single unit threatens the fleet — shared secrets in flash, or a device whose extracted firmware enables attacks elsewhere. If your keys are per-device and your firmware isn’t secret, Level 1 plus per-device identity gets you most of the protection and keeps your failure analysis.

STM32 readout protection levels 0, 1 and 2 with what each allows, whether it is reversible, and when to use it.
Most products should ship at Level 1.

Whichever you choose: test the transition on real hardware, early. The option-byte sequence, the timing of when it’s applied in production, and the interaction with your programming tooling all have sharp edges, and finding them during a production ramp is expensive.


Runtime: Authentic Isn’t the Same as Safe

Secure boot guarantees the code that started was yours. It says nothing about what that code does with a malformed Modbus frame.

Without TrustZone on an H7, your runtime tools are the MPU and the privilege split. What they’re genuinely worth:

Keep protocol parsers unprivileged. The Modbus and MQTT parsers handle attacker-controlled bytes. Run them unprivileged with an MPU region covering only their own stack and buffers. A parser bug then faults instead of writing wherever the bug points — it becomes a recoverable fault from article 07 rather than arbitrary code execution.

Make key material unreadable from parsing contexts. If the MPU denies the parser’s region access to the key storage area, a memory disclosure bug in the parser cannot leak keys, even if it’s exploitable for other things.

Mark flash regions no-execute where possible, so a buffer that receives downloaded data can’t become code.

None of this stops a determined attacker who finds a privileged bug. It converts a large class of parser bugs from “device compromise” to “device faults and records the reason,” and given that parser bugs are the most likely vulnerability you’ll actually ship, that conversion is worth the MPU configuration work.

Runtime isolation on an STM32H7 without TrustZone: protocol parsers run unprivileged in MPU regions limited to their own stack and buffers, with key storage inaccessible.
MPU isolation turns parser bugs into recorded faults.

Input validation is the other half, and article 07’s defensive boundary applies unchanged — lengths, indices, counts, ranges, checked at the point untrusted data enters, every time.


When Verification Fails

A production device must do something better than while(1).

The options and their trade-offs:

Boot the previous known-good image. Best for availability. Needs anti-rollback to distinguish “reverting to the image we were running yesterday” from “an attacker forcing a downgrade” — and that distinction is why the previous image’s security version must still be at or above the stored minimum.

Enter recovery mode. Article 07’s minimal firmware. Good, with one caveat worth stating: an attacker who can corrupt flash can force you into recovery mode. If recovery mode has fewer checks than the main image — and it usually does, because it’s small and was written under time pressure — you’ve given them a downgrade attack. Recovery firmware needs the same signature verification and the same scrutiny as the main image.

Halt in a safe state. Right for safety-critical products where operating on unverified code is worse than not operating. Wrong for most industrial gear, where a controller that refuses to boot is itself the incident.

Record the failure either way, with the reason and the rejected image’s version, in the backup-SRAM record article 07 built. A device that has rejected forty images in a week is telling you something that no single boot log would.

Three responses to a failed verification: boot the previous good image, enter recovery mode with the same checks, or halt in a safe state, with the failure recorded and retries bounded.
When verification fails, pick a path and record why.

And bound the retries. An attacker who can trigger unlimited verification attempts gets both a timing oracle and a power-consumption attack on a battery product. Verification failures should back off, and they should count.


The Regulatory Floor

Security is becoming a market-access requirement rather than a differentiator.

The EU Cyber Resilience Act sets obligations for products with digital elements sold into the EU: security by design, vulnerability handling for a defined support period, coordinated disclosure, and reporting of actively exploited vulnerabilities. The dates are staged: reporting of actively exploited vulnerabilities has applied since 11 September 2026, and the main obligations apply from 11 December 2027, so a product being architected today will ship into both.

The practical consequences reach straight back into this series. You need a way to ship security fixes for the product’s entire declared support life, which is article 12’s OTA architecture and article 10’s ten-year concerns. You need a software bill of materials, which means knowing every third-party component in your image and its version. And you need to know which units are running what, which is article 11’s observability.

None of that is bolt-on. Check the current dates and scope for your product category before committing to a compliance plan — this is a moving area and secondary sources disagree.


Next

Every mechanism in this article has the same weakness: it’s only as good as its failure path, and its failure path is the least-tested code in the product.

Does your bootloader actually reject an image with a flipped bit in the signature? Does anti-rollback actually block a downgrade, or does the comparison have an off-by-one? Does the parser actually fault instead of writing past its buffer? You can’t answer those by reading the code, and you can’t answer them by testing on hardware alone.

Which raises the question this series has deferred since article 01, when the interface pattern first appeared: how do you build firmware you can actually test — on a host, exhaustively, including the paths that only execute when something is wrong?

Article 09: Designing Firmware for Testability — unit testing, mocks, fault injection, and hardware-in-the-loop.


Series Index

Phase 1 — Architecture

Phase 2 — Implementation

Phase 3 — Failure & Resilience

Phase 4 — Verification

  • 09. Designing Firmware for Testability

Phase 5 — Product Lifecycle

  • 10. Designing Firmware for 10-Year Products
  • 11. Field Diagnostics & Observability
  • 12. OTA & Safe Firmware Updates

Leave a Reply

Your email address will not be published. Required fields are marked *