[ DOTSTAR_SYSTEMS ]

blog article

Wi-Fi Crypto Can't Go Fully PSA: Inside the Mbed TLS 4.x Migration

Wi-Fi security can never be fully PSA-compliant — the standards mandate legacy algorithms PSA won't touch. Here's what porting hostap's crypto to Mbed TLS 4.x and PSA actually took: a deleted DH module, dropped curves, deliberate hacks, and a detour through the bootloader — all riding into nRF Connect SDK 3.4.0 LTS.

Wi-Fi Crypto Can't Go Fully PSA: Inside the Mbed TLS 4.x Migration

A note before this gets going: this is my personal account of one slice of a much larger effort, written in a personal capacity. It’s not a statement from Nordic Semiconductor, and it’s not the full story of nRF Connect SDK 3.4.0 — just the Wi-Fi crypto corner of it, as I saw it from where I sat.

The one-line version

Wi-Fi security got ported onto Mbed TLS 4.x and the PSA Crypto API for nRF Connect SDK 3.4.0 LTS — and hit a ceiling, because the standards mandate legacy algorithms PSA will never accept.

Okay, that was the one line. Here’s why it’s more interesting than it sounds.

Nordic just shipped nRF Connect SDK 3.4.0 as its newest Long Term Support release, and riding into it is a rewrite of how Wi-Fi security talks to crypto: WPA3-Personal (SAE), WPA3-Enterprise, WPS, and DPP, all pulled off legacy Mbed TLS calls and moved onto Mbed TLS 4.x and TF-PSA-Crypto.

That sounds like a version bump. It isn’t. Mbed TLS 4.x changes which functions you’re allowed to call, pushing crypto behind the PSA Crypto API — and Wi-Fi security was leaning hard on the primitives that got taken away.

Here’s the part no release note will tell you: Wi-Fi security can’t fully make that move — not now, not ever. The standards mandate a pile of legacy algorithms PSA wants nothing to do with, and you don’t get to deprecate a client that still speaks them. So this was never “migrate Wi-Fi to PSA.” It was “push it onto PSA as far as PSA allows, then hack the rest” — for a stack that is, in real part, legacy by specification. This is what that actually took, and how far it reached before it was done. The work started in upstream Zephyr more than a year before this LTS tag existed; the release is just when it became someone’s supported baseline.

Whose work this actually is

It’s easy for an “I worked on X” post to quietly read as “I did X.” I didn’t, so let me be precise up front. Two groups drove this:

  • Valerio Setti, on the open-source Mbed TLS / TF-PSA-Crypto side, did the library-level surgery everything here depends on.
  • Nordic’s crypto team owns the NCS crypto stack (nrf_security, TF-M, the PSA driver wiring) that Wi-Fi security sits on top of.

My part was narrower and specific: owning the Wi-Fi crypto surface — hostap / wpa_supplicant’s use of Mbed TLS — and keeping WPA3-Personal, Enterprise, WPS, and DPP correct as the ground moved under them.

The deal that changed

For the whole Mbed TLS 3.x era, the deal was simple: the library handed you the primitives. Want a Diffie-Hellman group, an elliptic-curve point, a hash context? Include the header, call the mbedtls_* function, get your result. Protocol code reached straight into the crypto and manipulated the math directly.

Mbed TLS 4.x rewrites that contract. TF-PSA-Crypto becomes the actual crypto engine, and the PSA Crypto API becomes the interface you’re expected to use. The direct mbedtls_* primitives don’t disappear so much as retreat behind a wall — some deleted outright, others still there but with their headers moved out of the public tree. The mandate is clear: route your crypto through PSA. Three concrete changes carried the weight of it:

  • DHM is gone. The classic Diffie-Hellman module has no successor — neither a mbedtls_* function nor a PSA equivalent. Code that called it — WPS, whose key exchange is 1536-bit MODP DH — didn’t get a renamed function, it lost the implementation entirely.
  • Legacy ECP curves removed. secp192r1, secp224r1/k1 are gone. Anything offering them in a handshake now has to actively stop offering them, not lean on them quietly working.
  • Legacy crypto headers moved to mbedtls/private/. Every direct #include <mbedtls/<alg>.h> that bypassed the PSA API stopped resolving. Widest blast radius of the three, because reaching past the API to grab a primitive was completely normal in the 3.x world — and a great deal of code, in a great many places, did exactly that.

Here’s the actual inventory — what left the library, and where each piece had to end up:

Mbed TLS 3.x primitiveFate in 4.xWhere it lives nowWi-Fi feature it unblocks
DHM (mbedtls_dhm_*)Deleted — no successorCopied into hostapWPS (1536-bit MODP DH)
ECP curves secp192r1, secp224r1/k1RemovedDisabled from the handshakesTLS / EAP curve negotiation
Legacy headers mbedtls/<alg>.hMoved to mbedtls/private/Routed through PSA, or included from private/ on purposeAnything reaching past the API
Direct hash / HMAC / cipher callsSuperseded by PSApsa_* calls with imported keysSAE, DPP, EAP, key derivation

The first row is the interesting one. When a whole module is deleted with no replacement on either side of the wall, “port it” stops meaning “call the new function” and starts meaning “carry the implementation yourself”:

  Mbed TLS 3.x                    Mbed TLS 4.x + TF-PSA-Crypto
  ------------                    ----------------------------
  hostap --calls--> mbedtls_dhm_*      hostap --calls--> (nothing)
                    (lives in libmbedtls)      |
                                               +--> DHM now copied INTO hostap
                                                    (WPS 1536-bit MODP DH,
                                                     re-homed, not re-pointed)
DHM didn't move behind the PSA wall — it left the building. hostap had to bring its own.

Why Wi-Fi took this on the chin

To be clear about what this migration was and wasn’t: hostap and wpa_supplicant don’t use PSA opaque keys — key material still lives in the protocol code as raw bytes, imported into PSA for each operation. So this wasn’t a redesign toward a hardware key store. It was narrower and, in its own way, more finicky: push every crypto operation through the PSA API surface as far as it will gopsa_* calls with transparent, imported keys — and patch the spots where PSA simply doesn’t reach.

The “as far as it will go” ceiling, concretely: WPS still runs on 1536-bit MODP Diffie-Hellman and SHA-1, and the legacy EAP inner methods the field still uses — EAP-MD5, and the non-EAP methods tunneled inside EAP-TTLS like MSCHAPv2 and CHAP — are built on MD5/MD4/DES. PSA’s clean modern surface has no interest in any of that, but the standards mandate it, so it stays.

On top of that legacy floor, even the modern parts do arithmetic the PSA API doesn’t cleanly expose:

  • SAE (WPA3-Personal) derives its password element (PWE) by manipulating curve points and scalars directly — that’s the protocol, not an implementation shortcut, and PSA has no tidy call for it.
  • DPP does raw EC point work of its own for its provisioning exchange.
  • WPS needs a Diffie-Hellman that no longer ships in any form, so its exchange had to be rebuilt — DHM effectively copied back in — rather than re-pointed at a PSA function that doesn’t exist.
  • Enterprise (EAP-TLS/TTLS/PEAP) rides the TLS handshake directly, so every curve and cipher Mbed TLS 4.x dropped had to be checked against what EAP actually negotiates in the field — not against what compiles.

Put the whole security surface in one view and the “can’t go fully PSA” claim stops being rhetorical — it’s just what the column says:

Wi-Fi featureCrypto it needsPSA-native?The caveat
WPS1536-bit MODP DH, SHA-1DH deleted in 4.x — re-homed inside hostap
EAP-MD5MD5Mandated legacy; kept as-is
EAP-TTLS + MSCHAPv2 / CHAPMD4 / MD5 / DESInner methods the field still speaks
WPA3-SAEEC point & scalar math (P-256)~No tidy PSA call for PWE; raw ECP used
DPPRaw EC point ops~Provisioning math not cleanly PSA-exposed
WPA3-Enterprise (EAP-TLS)Modern EC + TLS 1.2/1.3Cleanest path — but still must drop removed curves

Only the last row is a clean PSA citizen. Everything above it is either legacy the spec won’t let you retire () or modern-but-awkward math PSA doesn’t expose directly (~). That distribution is the story: a real Wi-Fi security stack is mostly not the clean case.

Where PSA covered the operation, the work was mechanical: swap the mbedtls_* call for its psa_* equivalent, import the key, read the result back out. Where it didn’t — DHM, the dropped curves, the primitives now behind private headers — the honest move was a hack: reinstate the missing DH, disable dropped curves from the handshakes, reach into the private headers on purpose. Not workarounds dressed up as clean solutions; deliberate bridges over the gaps a maturing spec still had. That’s why this was slow rather than merely tedious: each of the four modes had to be re-verified against real handshakes, because a security stack that compiles but silently drops a mode is worse than one that fails to build. mac80211_hwsim carried the simulation side — Personal, Enterprise, WPS, DPP, end-to-end — running alongside vendor hardware validation (Nordic, NXP), not as a substitute for it.

The denser, mode-by-mode writeup of this lives as a case study on the site: CASE::003, MbedTLS 4.x Migration · Wi-Fi Security Stack.

What’s next: opaque keys, and splitting hostap in two

Everything above stops at one deliberate line: key material still lives in the protocol code as raw bytes, imported into PSA for each operation — transparent, not opaque. That’s the honest account of where this landed. It’s also the setup for the harder problem no one has finished yet.

Opaque keys are what PSA was really built for: material that is generated, stored, and used inside a secure domain — a PSA secure processing environment, TF-M on Nordic parts — and never crosses into the non-secure application where hostap runs. Getting Wi-Fi there isn’t a port, it’s a redesign. hostap / wpa_supplicant would have to be split along a secure / non-secure boundary: the key-touching crypto pushed to the secure side, the protocol logic left on the non-secure side driving it through key handles instead of bytes. And every operation PSA doesn’t expose yet has to be brought into the API rather than worked around — the way SAE’s PWE was pulled in by extending the PSA PAKE interface, repeated for the rest of the awkward math above.

That’s a topic in itself — arguably a project in itself — so it gets its own post rather than a cramped paragraph here. It’s the direction this is heading, and where I’m looking next. Watch this space.

A short detour through the bootloader

One deliberate decision is worth calling out, because it shows the intent behind all of this. There was an easy way to make legacy code keep building: copy the now-private Mbed TLS headers around so everything stays plug-and-play and nobody notices the primitives went private. I reverted that. The whole point of the migration is to make it visible that these APIs are legacy and shouldn’t be leaned on — papering over it defeats the purpose.

The concrete example is MCUboot, the bootloader most Zephyr and NCS targets use to verify their own images. It still builds on the older Mbed TLS 3.6.5, not 4.1.0 — but the change to include the primitive through its explicit private path, mbedtls/private/ecp.h, went in anyway. Not because the bootloader was forced off 3.6.5, but so the “this is private, don’t rely on it” signal is honest all the way down the stack instead of quietly hidden.

Where it landed

Kept short on purpose — the substance is above; this is just the receipt:

The takeaway

An LTS release note tells you what shipped. It won’t tell you that one line item started over a year earlier, crossed two upstream projects and reached into the bootloader on purpose, and re-plumbed four live security protocols onto a new crypto API — legacy algorithms, hacks, and all — without dropping a single mode. If you’re sizing up a crypto dependency bump in your own stack, that’s the question that actually matters: not “is there a PR,” but “how far does this reach, how much of it is legacy you can’t delete, and how hard was it exercised, before it becomes something you have to support for years.”


PS: This post was drafted with Claude Code from raw notes and verified against the public PR history on GitHub — every PR linked was checked for merge state and date before being cited. The work, the credit split, and the opinions are mine.

Dotstar Systems works on wireless security stacks at the seam where protocol meets crypto library — Wi-Fi, Bluetooth, cellular, on Linux, RTOS, or bare silicon. If you’re staring down a crypto migration that touches a live security protocol — and, it turns out, maybe your bootloader too — and want someone who’s already been through the DHM-shaped and ECP-shaped potholes, let’s talk.