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.
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/k1are 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 primitive | Fate in 4.x | Where it lives now | Wi-Fi feature it unblocks |
|---|---|---|---|
DHM (mbedtls_dhm_*) | Deleted — no successor | Copied into hostap | WPS (1536-bit MODP DH) |
ECP curves secp192r1, secp224r1/k1 | Removed | Disabled from the handshakes | TLS / EAP curve negotiation |
Legacy headers mbedtls/<alg>.h | Moved to mbedtls/private/ | Routed through PSA, or included from private/ on purpose | Anything reaching past the API |
| Direct hash / HMAC / cipher calls | Superseded by PSA | psa_* calls with imported keys | SAE, 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)
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 go — psa_* 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 feature | Crypto it needs | PSA-native? | The caveat |
|---|---|---|---|
| WPS | 1536-bit MODP DH, SHA-1 | ✗ | DH deleted in 4.x — re-homed inside hostap |
| EAP-MD5 | MD5 | ✗ | Mandated legacy; kept as-is |
| EAP-TTLS + MSCHAPv2 / CHAP | MD4 / MD5 / DES | ✗ | Inner methods the field still speaks |
| WPA3-SAE | EC point & scalar math (P-256) | ~ | No tidy PSA call for PWE; raw ECP used |
| DPP | Raw EC point ops | ~ | Provisioning math not cleanly PSA-exposed |
| WPA3-Enterprise (EAP-TLS) | Modern EC + TLS 1.2/1.3 | ✓ | Cleanest 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:
- Upstream cutover:
hostap#99(removing legacy Mbed TLS support ahead of time),zephyr#104031hostap#125(the Mbed TLS 4.x / PSA glue itself, both Valerio Setti’s).
- Wi-Fi crypto alignment:
hostap#135and the EC/PK follow-ons#137,#140,#141. - Bootloader:
mcuboot#2732, “Use mbedtls/private paths for TF-PSA-Crypto.” - NCS backports that made
v3.4.0:sdk-nrf#29311,#29368,#29376— merged three weeks before the tag, not after it.
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.