Skip to content

Add WPA2-Enterprise (PEAP/MSCHAPv2) support - #287

Closed
dantdmdarealguy wants to merge 1 commit into
OpenIntelWireless:masterfrom
dantdmdarealguy:wpa2-enterprise
Closed

dantdmdarealguy wants to merge 1 commit into
OpenIntelWireless:masterfrom
dantdmdarealguy:wpa2-enterprise

Conversation

@dantdmdarealguy

@dantdmdarealguy dantdmdarealguy commented Oct 5, 2026 •

Copy link
Copy Markdown

Closes: #4 · Requires: OpenIntelWireless/itlwm#1073. Older kexts don't have the new ioctls; HeliPort detects that and asks the user to update itlwm.

Summary

Adds a userspace 802.1X supplicant so HeliPort can join WPA2-Enterprise networks
with itlwm. All EAP, TLS and certificate handling happens here; the kext only
relays EAPOL frames and installs the derived PMK (see the itlwm PR, OpenIntelWireless/itlwm#1073).

How it works

  • EAP engine: vendored hostap eap_peer with its internal TLS implementation
    (EAPCore/, BSD license, no OpenSSL), driven by a small RFC 4137 lower layer
    (EAPSupplicantBridge.c). PEAPv0 outer, MSCHAPv2 inner; other methods are NAK'd.
  • Certificate trust (trust on first use): the first connection to an SSID runs a
    probe-only TLS handshake that aborts right after the server certificate, so no
    credentials are sent. HeliPort shows the subject and SHA-256 fingerprint; on
    Trust it pins the certificate per SSID. Later connections verify the pin inside
    hostap (hash://server/sha256/…) before phase 2, so credentials only ever go to
    the pinned certificate. A changed certificate is reported and the attempt fails.
  • Transport: EAPOL frames move between HeliPort and itlwm through new
    user-client ioctls (RX_EAPOL / TX_EAPOL).
    • itlwm queues incoming frames, so HeliPort polls: every 10 ms during an
      exchange, every 250 ms otherwise (enough to catch an AP-initiated reauth).
    • No privileged helper and no extra entitlements; unsigned CI builds work.
    • Unsigned builds still work, but Keychain "Always Allow" doesn't stick for a
      completely unsigned app, so the release zip is ad-hoc signed.
  • Credentials are stored in the Keychain via the existing CredentialsManager;
    "Remember this network" and auto-join work as for personal networks.
  • Reauthentication: after connecting, HeliPort keeps answering EAP requests on
    the same association (periodic reauth, session timeouts), including after a
    HeliPort relaunch while still connected (credentials restored from the Keychain).

Other fixes

  • ClientKit: get_platform_info() built driver_version + " " + fw_version with
    strcpy/strcat into a 32-byte field and could overflow the caller's stack.
  • WiFiConfigWindow: the Join button checked the username box's own visibility
    instead of its grid row, the username box had no delegate, and enterprise
    passwords were truncated to 64 / required ≥ 8 characters (WPA passphrase rules).

UI

  • Enterprise security types show the username field; the trust prompt appears on
    first connect.

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, itlwm from Add 802.1X (WPA2-Enterprise) support for a userspace supplicant itlwm#1073.
  • Texas A&M TAMU_WiFi (PEAP/MSCHAPv2): first connect with certificate prompt,
    repeat connect with pinned certificate, Remember + auto-join at login,
    reauthentication (notifyutil -p org.openintelwireless.HeliPort.debug.reauth
    triggers one for testing).
  • SwiftLint: no new violations; it reports the same 8 warnings as master, from
    a rule newer SwiftLint versions add. The CI build commands (Debug and Release,
    unsigned) succeed.

Limitations / follow-ups

  • PEAP/MSCHAPv2 only (no EAP-TLS/TTLS, no WPA3-Enterprise). No UI to view or reset
    a pinned certificate yet (forgetting the network clears it).
  • Brief outage on reauthentication. After a reauth, one AP ran a second EAP
    exchange about 3 s later, and connectivity dropped for about 1.4 s (see the itlwm
    PR).
  • Tested on one network so far. Reports from other enterprise networks are welcome.
  • EAPVerboseLogging (defaults) enables hostap debug logging; it includes
    credential-derived hexdumps, so it is off by default.

Adds a userspace 802.1X supplicant so HeliPort can join WPA2-Enterprise
networks with itlwm. All EAP, TLS and certificate handling is here; itlwm
only relays EAPOL frames and installs the derived PMK.

- EAP engine: vendored hostap eap_peer with its internal TLS client
  (EAPCore/, BSD, no OpenSSL), driven by a small RFC 4137 lower layer
  (EAPSupplicantBridge.c). PEAPv0 outer, MSCHAPv2 inner.
- Server certificate trust on first use: the first connection to an SSID
  runs a probe-only TLS handshake that stops after the server certificate,
  so no credentials are sent. The user checks the subject and SHA-256
  fingerprint and pins it; later connections verify the pin in hostap
  before phase 2.
- EAPOL frames move through itlwm's RX_EAPOL / TX_EAPOL user-client calls,
  so no privileged helper or extra entitlements are needed and unsigned
  builds work. Older itlwm builds are detected and the user is told to
  update.
- Credentials in the Keychain; Remember / auto-join work as for personal
  networks. Reauthentication is answered on the established association,
  also after a HeliPort relaunch.
- Fix a stack overflow in get_platform_info() (strcat into a 32-byte
  field), and Join-button and credential validation in WiFiConfigWindow
  for enterprise networks.

For testing reauthentication:
notifyutil -p org.openintelwireless.HeliPort.debug.reauth

Closes OpenIntelWireless#4.

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

Certificate-change reporting, PMK delivery failure handling, pin removal, and secure PMK erasure need correction.

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

Open (3)
What changed in this PR

Adds a userspace PEAP/MSCHAPv2 supplicant that integrates with the new itlwm 802.1X transport and PMK interfaces.

Changes:

  • Adds EAPOL transport, authentication lifecycle, reauthentication, and certificate pinning.
  • Vendors hostap EAP, TLS, and cryptographic components.
  • Adds enterprise credential UI and ClientKit ioctl support.
File Description
ClientKit/​{Api.c,Api.h,Common.h,IoctlId.h} Adds enterprise association, EAPOL, and PMK APIs.
HeliPort/​EAPSupplicant/​* Implements the supplicant, transport, and certificate pins.
HeliPort/​NetworkManager.swift Integrates enterprise association and auto-join.
HeliPort/​Appearance/​WiFiConfigWindow.swift Adds enterprise credential handling.
HeliPort/​Appearance/​StatusMenu/​* Manages session teardown and reattachment.
HeliPort/​Bridge.h Exposes the C supplicant bridge to Swift.
HeliPort.xcodeproj/​project.pbxproj Adds new sources to the application target.
EAPCore/​EAPSupplicantBridge.{c,h} Bridges Swift transport to hostap’s EAP peer.
EAPCore/​{README.md,build_config.h} Documents and configures the vendored core.
EAPCore/​common/​* Provides shared protocol definitions.
EAPCore/​crypto/​* Provides internal cryptographic primitives.
EAPCore/​eap_common/​* Provides shared EAP and PEAP routines.
EAPCore/​eap_peer/​* Implements PEAP/MSCHAPv2 peer authentication.
EAPCore/​tls/​* Implements TLS and X.509 processing.
EAPCore/​utils/​* Provides hostap utility infrastructure.

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

Comment thread ClientKit/Api.c
Comment on lines +352 to +354
// Don't let a derived PMK linger in this stack frame any longer than
// the syscall that consumes it needs it for.
memset(&req, 0, sizeof(req));
b->cb.notify_eap_error = cb_notify_eap_error;
b->cb.set_anon_id = cb_set_anon_id;

b->sm = eap_peer_sm_init(b, &b->cb, b, &b->eap_conf);
Comment on lines +53 to +54
func removePin(forSSID ssid: String) {
try? keychain.remove(ssid)
@dantdmdarealguy

Copy link
Copy Markdown
Author

Superseded by #288.

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