From a series of notes on bringing up our own OS on a Samsung Galaxy Tab S6 (SM‑T865, Snapdragon 855). "The Parasite in the Tablet" explained that the Wi‑Fi firmware here runs on the cellular modem's Hexagon and encrypts traffic itself; "23 Milliseconds" — how it tests the host. This note is about what we gained by writing a WPA supplicant on the host, and what we did not gain, because on such a chip it cannot be gained.
1. The question
We wrote 802.11i ourselves: the WPA2‑PSK 4‑way handshake, the group handshake and SAE for WPA3‑Personal — a pure crate crates/wpa, no_std, without radio or clock, a state machine over bytes that the driver feeds EAPOL frames and reads keys from. It is natural to ask why: the WCN3990 firmware encrypts every frame anyway (hwcrypto), and the traffic key leaves for it. What does a supplicant on the host protect if the host does not encrypt? The short answer is credentials, not traffic. Below — why, what it gives, and why the boundary runs here for everyone, not only for us.
2. What was built
crates/wpa — five modules and a reference:
crypto— the primitives of 12.7.1: PSK from the password (PBKDF2‑SHA1, 4096 rounds, the SSID as salt — the WPA2‑Personal PMK), PRF‑SHA1 and KDF‑SHA256 expanding the PMK into the PTK, HMAC‑SHA1‑128 and AES‑128‑CMAC MICs, AES key wrap (RFC 3394). On top of the same RustCrypto crates TLS already had.eapol— EAPOL‑Key frames, KDEs (GTK, IGTK), MIC signing and verification under the descriptor version set by the AKM.supplicant— the station: M1 → PTK → M2; M3 (MIC, key data under the KEK, the AP's RSN element must match the beacon — otherwiseRsnMismatch) → M4 andKeys {ptk, gtk, igtk}upwards;Rekeyvia the group handshake.authenticator— the AP side, a mirror. Today it is run by the fake radio in tests, so the handshake is proven from both ends; this OS's access point on this chip does not run it yet.sae— Dragonfly (RFC 7664) over P‑256: the password element by hunting‑and‑pecking, commit,KCK ‖ PMK, confirm, the anti‑clogging token; we do not speak hash‑to‑element and answersae_group_refusedto routers that insist on it.vectors— an independent reference (Python:hashlib,hmac,cryptography, P‑256 by the curve equation): four messages byte for byte, the PWE, both commits, KCK/PMK/PMKID, both confirms.
There is no unsafe in the crate: parsing of frames sent by the AP — or by the firmware on its behalf — is done in safe Rust, and an error there is a panic, not a write past a buffer.
3. How it passes through the firmware
The password's path. It is stored in the kernel store under the key sys/net/wifi (WifiSettings {ssid, password}, JSON, in plain text; the net.settings tool shows only password_set). The station (tools::wifi::station) hands it downwards as LinkAsk::Connect {bssid, password}; the radio task adds the SNonce and rand ‖ mask for SAE from Kernel::random and builds Credentials. Link::connect immediately computes from the password the PMK (wpa::psk) or the password element and the SAE commit. Neither the password nor the PMK goes further than Link.
The keys' path is in the diagram: on the left the host, on the right what the firmware sees.
host (safe Rust) firmware (Hexagon, hwcrypto)
─────────────────────────────── ────────────────────────────
password ──PBKDF2──▶ PMK (WPA2) —
password ──SAE/P‑256──▶ PMK (WPA3) commit/confirm ▶ sees Auth frames (public
MGMT_TX_SEND scalar/Element), not the password
PMK + ANonce + SNonce ──PRF/KDF──▶ PTK = KCK ‖ KEK ‖ TK
EAPOL M1..M4 ▶ sees nonces, MICs and key data
TX_FRM NO_ENCRYPT (wrapped under KEK) — in plain
text, but without PMK/KCK/KEK
TK (16 bytes) ───────────────────── VDEV_INSTALL_KEY ▶ pairwise traffic key
GTK (from M3, RSC=0) ────────────── VDEV_INSTALL_KEY ▶ group key
IGTK (from M3) — accepted, not installed —
──────────────────────────────── PEER_SET_PARAM ▶ authorize: data may flow
encrypts and decrypts
every frame of this sessionThe facts from the code the diagram rests on.
EAPOL goes through the firmware in the clear and before the keys. The first data frame the station hears at all after association is M1 from the receive ring. The replies go out as the HTT command TX_FRM with the NO_ENCRYPT flag — exactly how mac80211 sends EAPOL before keys. The handshake is announced to the firmware in advance: PEER_ASSOC carries NEED_PTK_4_WAY, and authorize at the end means "data may flow".
Only the TK and the GTK leave for the firmware. install_ptk takes from Keys exactly ptk.tk — 16 bytes for CCMP — and installs it with VDEV_INSTALL_KEY {PAIRWISE, AES_CCM, idx 0} for the BSSID address. The KCK and KEK, which sign the frames and wrap the key data, stay on the host; after M4 the Supplicant object is dropped together with them. The GTK is installed as {GROUP, group cipher, idx}; the RSC that M3 brought is read by the host, but zero is written into the command — "the RSC is left to the firmware, as ath10k_install_key leaves it". Then PEER_SET_PARAM AUTHORIZE — and the link is connected. In the wifi.link cell this is keys_set 2.
The firmware does not touch SAE. Commit and confirm travel as Authentication frames via MGMT_TX_SEND/MGMT_RX, like open authentication; ath10k has no SAE offload, and neither do we. The password element, rand, mask and the whole PMK derivation are on the host; the firmware sees only the public scalar and Element in the air.
MFP — halfway. The RSN element for SAE announces MFPC|MFPR and BIP‑CMAC‑128, the IGTK from M3 is read into Keys.igtk ("IGTK id 5 received" in a live run), but it is not installed into the firmware: ath10k hands BIP back to software, and the verification of protected Deauth/Disassoc on our side is not yet written; we do not set the PMF flag in PEER_ASSOC — the firmware's support of MFP_SUPPORT is unknown. The gap is recorded.
4. What a supplicant on the host protects
The password and the PMK. The firmware receives neither the password, nor the PMK, nor the KCK/KEK — by no protocol path. What it sees in EAPOL (nonces, MICs, wrapped key data) is public in the air anyway; the PTK cannot be derived from it without the PMK. Consequence: compromised firmware cannot join the network as another device with our credentials and cannot derive the keys of other sessions on the same network — for WPA2‑PSK that would require the PSK, for SAE the password. It receives its own TK for this session, and only that.
A fresh PMK in WPA3. In WPA2‑Personal the PMK equals the PSK — one for all sessions and all devices on the network, and a captured handshake is brute‑forced offline. In SAE the PMK is derived from the Dragonfly exchange and is unique to the session; nothing can be brute‑forced from a capture, and the disclosure of one PMK does not open other sessions. On the tablet the PWE and two scalar multiplications took ~60 ms together with the air.
The logic and the parsing are ours. MIC verification, unwrapping the key data, comparing the RSN element with the beacon (a cipher downgrade in the middle), checking the scalar and the point in SAE, reflections, statuses — all in safe Rust on the host, against an independent reference, not in a vendor blob. What the supplicant concludes about a "wrong password" (bad_password) we know, rather than guess from the firmware's behaviour.
Two caveats, both from our own documents. First: "the firmware cannot learn the password" is true by protocol. While DMA stream 0x640 goes without SMMU translation, the firmware reads all of physical RAM — including sys/net/wifi in plain text and the PMK in Link. The WPA boundary becomes complete only after stage‑1 from the threat model is enabled; until then the supplicant protects the credentials from the air, but not from the co‑processor. Second: the SNonce and SAE's rand/mask are taken from Kernel::random, and on sdm855 there is no entropy device yet — "the nonces are the clock's", SplitMix64 from uptime. This is recorded as non‑cryptographic; the "freshness" of the PMK today rests on a predictable source, and this must be fixed before the network becomes anything more than a test.
The same boundary on a fragment of the general device map; the diagram above showed how the secrets are derived one from another, this one shows where each of them physically lies. The password — in the heap, in the store; the PMK, KCK and KEK — there too, in Link, and briefly; the TK and GTK — in the firmware's key store; EAPOL and data frames — in plain text in LENT_WLAN, where the copy engines DMA. Bold arrows are the key's path; dashed — what the firmware can do if it wants to.
The full map of all tenants, with the same node names, is in Whose Computer Is This?, § 2a.
5. What a supplicant on the host does not protect
The session traffic. The TK in the firmware means every frame of this session is plain text for it in both directions, by definition: it is the one encrypting it. Outgoing Ethernet frames the host puts into LENT_WLAN unencrypted; incoming ones it receives already decrypted, in native‑Wi‑Fi decap. No logic on the host changes this.
What really goes into the air. The firmware holds the key store and decides itself what to encrypt with. It can install any other key for itself, send a frame unencrypted or under someone else's key — the host will not see it. The encrypted flag in the receive descriptor, by which Link::on_eth judges whether a protected frame was decrypted, is its word, like all the contents of the descriptors. There is no verifiable invariant here, only trust.
Forward secrecy against the firmware. SAE gives a fresh PMK per session, but the TK of every session is handed to the firmware. Compromised at moment T, it knows the traffic of all sessions from T onward; of past ones — only if it recorded them in advance, which runs into its own persistence, about which we know nothing. The freshness of the PMK protects against passive interception in the air, not against the encrypting co‑processor.
Management frames. While the IGTK is not applied, a Deauth in the AP's name is accepted without verification — both from the air and from the firmware, which for losing the link also has its own event PEER_STA_KICKOUT.
6. The alternative: encrypt on the host
The logical way out is not to hand over the TK: encrypt CCMP on the host, hand the firmware ready 802.11 frames "as is", and receive undecrypted ones. This requires a mode in which the firmware does not touch cryptography on either transmit or receive.
Our sources say little about it. In htt.rs there is decap::RAW — the receive format "as it came from the air: 802.11 header, encryption parameters, payload, FCS" — and the MAC_HDR_PRESENT flag on transmit; but WMI_INIT requests RX_DECAP_NATIVE_WIFI, and the whole data path is built on it. Neither ATH10K_HW_TXRX_RAW nor "raw mode" are mentioned in the code or the documents; in Linux ath10k has a rawmode parameter, but whether the WLAN.HL.3.2.0 firmware on the WCN3990 supports it and what is lost, we have not checked. This is an option requiring a live check, not an available path — and even if it succeeds it changes little: the firmware stops seeing the frames' contents, but stays with DMA into our window, the air and the cellular uplink.
7. Is Linux different?
No — the boundary is the same in form. wpa_supplicant in userspace computes the PMK and PTK, hands the temporal key to the kernel via nl80211 (NL80211_CMD_NEW_KEY), mac80211 calls the driver's set_key, ath10k_set_key assembles WMI_VDEV_INSTALL_KEY — the same command as our install_ptk. That is why the threat model in the "traffic keys" column puts the same thing for stock Android, mainline Linux and us: "in the firmware". Software encryption in mac80211 is enabled where the driver declines the key — as ath10k does for BIP — not by the user's choice. A supplicant on the host is neither our peculiarity nor our shortcoming, but the universal form of the boundary for any radio whose firmware holds hwcrypto.
8. Summary
WPA on the host protects credentials, not traffic. The password, the PMK and the handshake keys do not leave the host by any protocol path; the firmware receives exactly 16 bytes of TK and the GTK for one session and can neither join the network as someone else, nor open other sessions, nor — in WPA3 — brute‑force the password from a capture. The traffic of this session it sees in full, because it encrypts it itself, and that is not fixed by a single line in the supplicant.
Hence the consequence the whole series leads to: confidentiality of content against the radio firmware is built above the link — TLS with certificate verification and end‑to‑end encryption. What that looks like on a small board — in Trust the Wire?; what remains after all the fences — in "The Parasite in the Tablet" (§ 2.4, § 9); the map of all the device's tenants — in the series introduction.
Sources in the repository: the supplicant — crates/wpa/src/lib.rs, supplicant.rs (Ptk, Keys, Step, derive_ptk), authenticator.rs, eapol.rs, crypto.rs (psk), sae.rs (module documentation, password_element), vectors.rs; the keys' path — crates/ath10k/src/link.rs (module documentation, Credentials, Secret, connect, on_data, send_eapol, install_ptk, install_gtk, connected, on_eth), crates/ath10k/src/wmi.rs (key, peer_param, InstallKey), crates/ath10k/src/htt.rs (tx_flag0::NO_ENCRYPT, decap::RAW); the password and the station — crates/tools/src/net.rs (WIFI_KEY, WifiSettings), crates/tools/src/wifi.rs, wifi/station.rs, wifi/target.rs (LinkAsk::Connect, Kernel::random). Stages 8, 9, 9b with live runs — docs/wifi-sm8150.md § 6e–§ 6g; what remains after stage‑1 and the comparison with Linux — docs/wifi-threat-model.md § 6.1, § 7.
VPN Chrome extension: what it protects and what it doesn'tA Chrome VPN extension is a proxy for browser traffic only. What it really protects, where it leaks, and when you need a full VPN app instead.
Turnkey smart homes on our own OS: 0xd-os and 0xd-uiWe build turnkey smart homes on components we designed ourselves. At the core is 0xd-os — one operating system that runs identically on a flagship tablet and on a device with 512 KB of memory.
Why security agents need a sandboxAn agent that can 'just run tools' on a security review is a second attacker — unless every call has a grant. AGI Core isolates tool execution; Forge keeps the skill list in UIDE.