Skip to content

Add 802.1X (WPA2-Enterprise) support for a userspace supplicant - #1073

Closed
dantdmdarealguy wants to merge 2 commits into
OpenIntelWireless:masterfrom
dantdmdarealguy:wpa2-enterprise
Closed

dantdmdarealguy wants to merge 2 commits into
OpenIntelWireless:masterfrom
dantdmdarealguy:wpa2-enterprise

Conversation

@dantdmdarealguy

@dantdmdarealguy dantdmdarealguy commented Oct 5, 2026 •

Copy link
Copy Markdown

Related: #101 (also asked in #230, #240), OpenIntelWireless/HeliPort#4, OpenIntelWireless/HeliPort#287
Includes the one-line fix from #1072 as its first commit; merge that first or together.

Summary

itlwm can't join WPA2-Enterprise networks today. This adds the minimum kernel
support needed for a userspace EAP supplicant (implemented in HeliPort, OpenIntelWireless/HeliPort#287) to do it.
In line with the concern raised in #101, the kext has no certificate handling, TLS
or EAP logic
. It only moves EAPOL frames and installs the PMK that userspace
derives. The existing net80211 4-way handshake code is reused unchanged.

Changes

  • EAPOL receive path. On 802.1X networks, non-Key EAPOL frames (EAP-Packet /
    Start / Logoff) are handed to itlwm through a global hook
    (ieee80211_eapol_input). itlwm queues them in a small ring buffer (16 frames)
    for userspace. Previously ieee80211_eapol_key_input() dropped them. EAPOL-Key
    frames stay in the kernel for the 4-way handshake.
    • struct ieee80211com is unchanged, so the HAL softcs that embed it keep their
      layout.
    • Only the itlwm build is affected; AirportItlwm (IO80211FAMILY_V2) is untouched.
  • New user-client calls, appended after the existing IDs:
    • IOCTL_80211_ASSOCIATE_ENTERPRISE: join an SSID with RSN, AKM 802.1X and no PSK.
    • IOCTL_80211_TX_EAPOL: transmit an EAPOL frame built in userspace. It's needed
      because the interface link stays down until the 4-way handshake completes, and
      macOS drops ordinary writes on a down interface. Only EtherType 0x888E is
      accepted.
    • IOCTL_80211_RX_EAPOL: fetch the next queued frame. It returns length 0 when
      the queue is empty.
    • ID 13 is reserved and always rejected, because kexts without Fix out-of-bounds dispatch in ItlNetworkUserClient::externalMethod #1072 panic on it.
      Clients detect support by calling RX_EAPOL; older kexts reject that selector
      safely.
  • IOCTL_80211_WPA_KEY was a no-op stub. It now delivers the PMK after EAP, or a
    failure status, which deauths.
    • The PMK goes into the PMKSA cache under the negotiated AKM, the same mechanism as
      OpenBSD's SIOCS80211KEYAVAIL, so ieee80211_recv_4way_msg1() picks it up
      as-is.
    • It's only accepted for the SSID the driver is currently joining.
  • externalMethod() checks the input/output size against each selector's
    struct before the handler casts the buffer.
  • Enterprise association requests AKM 802.1X only (no SHA256-802.1X / 802.11w).
  • ClientKit headers updated to match HeliPort's copy.

Prebuilt binaries

Until this is merged, an unofficial prebuilt itlwm.kext + HeliPort.app built from these PRs is on my fork's releases page: https://github.com/dantdmdarealguy/itlwm/releases. The two only work together, and only with itlwm (not AirportItlwm).

Testing

  • macOS 15.7.7, Intel Wireless-AC 9560 (8086:34F0, subsystem 0034), OpenCore 1.0.7.
  • Texas A&M TAMU_WiFi (PEAP / MSCHAPv2):
    • first connect;
    • auto-join of the saved network at login;
    • reauthentication on an established association (supplicant-triggered
      EAPOL-Start).
  • Personal (PSK) and open networks unaffected.
  • The CI targets build: itlwm and AirportItlwm (all) (Debug), plus itlwm
    Release.

Known limitations

  • PEAP/MSCHAPv2 only (method support lives in HeliPort); WPA2-Enterprise only.
  • Brief outage on reauthentication. After a supplicant-triggered reauth on
    TAMU_WiFi, the AP ran a second full EAP exchange about 3 s later. Both
    succeeded, but connectivity dropped for about 1.4 s.
    • I suspect the AP reuses the replay counter on Message 1, which
      ieee80211_recv_4way_msg1() rejects.
    • I left the key-handling code alone rather than relax that check.
  • Tested on one network and one card so far.

selector == IOCTL_ID_MAX passed the bounds check and indexed one past the
end of sMethods[], calling through whatever pointer followed (a NULL in
practice, panicking the kernel). selector is unsigned, so the < 0 half of
the check was dead code.
itlwm can't join WPA2-Enterprise networks today. This adds the minimum
kernel support for a userspace EAP supplicant (HeliPort) to do so. No
EAP, TLS or certificate handling is in the kext: it only moves EAPOL
frames and installs the PMK that userspace derives. The existing net80211
4-way handshake code is reused unchanged.

- For 802.1X AKMs, non-Key EAPOL frames (EAP-Packet/Start/Logoff) are
  handed to itlwm through a global hook and queued in a small ring buffer
  for userspace, instead of being dropped by ieee80211_eapol_key_input().
  EAPOL-Key frames stay in the kernel. Only the itlwm build is affected
  (not IO80211FAMILY_V2 / AirportItlwm). struct ieee80211com is unchanged.
- New user-client calls, appended after the existing IDs:
  - IOCTL_80211_ASSOCIATE_ENTERPRISE: join an SSID with RSN, AKM 802.1X
    and no PSK.
  - IOCTL_80211_TX_EAPOL: transmit a userspace-built EAPOL frame. Needed
    because the link stays down until the 4-way handshake and macOS drops
    ordinary writes on a down interface. Only EtherType 0x888E accepted.
  - IOCTL_80211_RX_EAPOL: fetch the next queued EAPOL frame.
  - ID 13 is reserved and rejected: kexts without the bounds fix panic on
    it, so new clients probe with RX_EAPOL instead.
- IOCTL_80211_WPA_KEY (previously a no-op stub) delivers the PMK, or a
  failure status that deauths, after EAP. The PMK goes into the PMKSA
  cache for the negotiated AKM, the same mechanism as OpenBSD's
  SIOCS80211KEYAVAIL, so ieee80211_recv_4way_msg1() picks it up as-is.
- externalMethod now also checks the input struct size per selector.
- ClientKit headers updated to match HeliPort's copy.

Tested on macOS 15.7.7 with an Intel Wireless-AC 9560 on a PEAP/MSCHAPv2
network (Texas A&M TAMU_WiFi): first connect, auto-join at login, and
reauthentication. PSK and open networks are unaffected.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

EAPOL routing, callback lifecycle, PMK concurrency, and WPA protocol selection contain unresolved correctness issues.

Review effort: Balanced
Findings: 3 High severity · 2 Medium severity

Open (5)
What changed in this PR

Adds kernel-side WPA2-Enterprise support for a userspace EAP supplicant while retaining the existing four-way handshake implementation.

Changes:

  • Adds enterprise association and PMK installation calls.
  • Relays non-Key EAPOL frames through a bounded queue.
  • Validates user-client buffer sizes and reserves selector 13.
File Description
itlwm/​itlwm.hpp Declares enterprise association and EAPOL queue state.
itlwm/​itlwm.cpp Implements association, queueing, and hook lifecycle.
itlwm/​ItlNetworkUserClient.hpp Declares new user-client handlers.
itlwm/​ItlNetworkUserClient.cpp Implements EAPOL transport and PMK delivery.
itl80211/​openbsd/​net80211/​ieee80211_var.h Exposes the EAPOL callback hook.
itl80211/​openbsd/​net80211/​ieee80211_input.c Routes non-Key EAPOL frames to userspace.
include/​ClientKit/​IoctlId.h Adds enterprise ioctl selectors.
include/​ClientKit/​Common.h Defines enterprise ioctl payloads and statuses.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +580 to +581
extern void (*ieee80211_eapol_input)(void *, mbuf_t);
extern void *ieee80211_eapol_input_arg;
Comment on lines +383 to +386
enum ieee80211_akm akm = (enum ieee80211_akm)ic->ic_bss->ni_rsnakms;
if (!ieee80211_is_8021x_akm(akm))
akm = IEEE80211_AKM_8021X;
if (ieee80211_pmksa_add(ic, akm, ic->ic_bss->ni_bssid, req->pmk, 0) == NULL)
Comment thread itlwm/itlwm.cpp
Comment on lines +404 to +405
ieee80211_eapol_input_arg = this;
ieee80211_eapol_input = &itlwm::eapolInput;
Comment on lines +1153 to +1155
if (ieee80211_is_8021x_akm((enum ieee80211_akm)ni->ni_rsnakms) &&
mbuf_len(m) >= sizeof(*eh) + 2 &&
((const uint8_t *)(eh + 1))[1] != EAPOL_KEY) {
Comment thread itlwm/itlwm.cpp
wpa.i_enabled = 1;
wpa.i_ciphers = 0;
wpa.i_groupcipher = 0;
wpa.i_protos = IEEE80211_WPA_PROTO_WPA1 | IEEE80211_WPA_PROTO_WPA2;
@dantdmdarealguy

Copy link
Copy Markdown
Author

Superseded by #1075.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants