From 2db148c062fcfce9c28f7a2aaf8156b560136054 Mon Sep 17 00:00:00 2001 From: Hauke Mehrtens Date: Sun, 19 Apr 2026 12:50:08 +0200 Subject: [PATCH] wifi-scripts: ucode: advertise Transition Disable on WPA3-only BSSes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit WPA3 Specification v3.5 §13 defines the Transition Disable element sent inside message 3 of the 4-way handshake. An AP that is no longer offering a transition mode for its SSID sets the matching bit so that compliant STAs permanently stop falling back to WPA-PSK / WPA-EAP / open for that SSID, hardening against downgrade attacks and against operator mistakes where a transition-mode BSS is briefly brought up on an SSID that previously ran WPA3-only. Expose this as a UCI list 'transition_disable' with three classes of entries: * The existing OpenWrt encryption tokens 'sae' (bit 0x01), 'sae-pk' (0x02), 'wpa3' (0x04) and 'owe' (0x08) OR into the bitmap. SAE-PK itself is not yet wired through wifi-scripts; the token only lets an operator who configured SAE-PK out of band also hand the matching bit to hostapd. * 'on' derives the bitmap from the AP's auth_type ('sae' -> 0x01, 'eap2'/'eap192' -> 0x04, pure 'owe' -> 0x08) and overrides any other explicit tokens in the same list. Transition BSSes (psk-sae, eap-eap2, owe with owe_transition set) produce no bits even under 'on' because they are by definition still in transition. * 'off' unconditionally suppresses the element regardless of any other entries. Operators who need to revert a WPA3-only SSID back to a transition mode can set this proactively, giving compliant STAs time to forget the permanent bit before the mode change. Leave the list unset by default. Advertising Transition Disable is a one-way door -- once a compliant STA has seen the permanent bit for an SSID it will refuse to associate to a transition-mode BSS of the same name ever again -- so it must be opted in to per SSID, never flipped on by a firmware bump. This also matches the WPA3 and Wi-Fi Enhanced Open Deployment and Implementation Guide v1.1 Table 4 requirement that Transition Disable be MAND disabled by default on APs. Co-Authored-By: Claude Opus 4.7 Link: https://github.com/openwrt/openwrt/pull/23009 Signed-off-by: Hauke Mehrtens --- .../usr/share/schema/wireless.wifi-iface.json | 8 ++++ .../files-ucode/usr/share/ucode/wifi/ap.uc | 38 +++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/package/network/config/wifi-scripts/files-ucode/usr/share/schema/wireless.wifi-iface.json b/package/network/config/wifi-scripts/files-ucode/usr/share/schema/wireless.wifi-iface.json index 97dea0e42d..c32a49170c 100644 --- a/package/network/config/wifi-scripts/files-ucode/usr/share/schema/wireless.wifi-iface.json +++ b/package/network/config/wifi-scripts/files-ucode/usr/share/schema/wireless.wifi-iface.json @@ -1040,6 +1040,14 @@ "description": "Local time zone as specified in 8.3 of IEEE Std 1003.1-2004", "type": "string" }, + "transition_disable": { + "description": "Transition modes the AP signals as disabled per WPA3 v3.5 §13. Entries 'sae', 'sae-pk', 'wpa3', 'owe' are OR'd into the bitmap; 'on' (or '1') derives it from auth_type; 'off' (or '0') suppresses the element. Unset by default.", + "type": "array", + "items": { + "type": "string", + "enum": [ "on", "off", "0", "1", "sae", "sae-pk", "wpa3", "owe" ] + } + }, "uapsd": { "type": "alias", "default": "uapsd_advertisement_enabled" diff --git a/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/ap.uc b/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/ap.uc index 400034e4a9..b2890ff691 100644 --- a/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/ap.uc +++ b/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/ap.uc @@ -439,6 +439,42 @@ function iface_mfp(config) { ]); } +function iface_transition_disable(config) { + if (config.wpa < 2) + return; + + let list = config.transition_disable; + if (!list || !length(list)) + return; + + for (let s in list) + if (s == 'off' || s == '0') + return; + + let bits = 0; + for (let s in list) { + if (s == 'on' || s == '1') { + bits = 0; + switch (config.auth_type) { + case 'sae': bits = 0x01; break; + case 'eap2': + case 'eap192': bits = 0x04; break; + case 'owe': if (!config.owe_transition) bits = 0x08; break; + } + break; + } + switch (s) { + case 'sae': bits |= 0x01; break; + case 'sae-pk': bits |= 0x02; break; + case 'wpa3': bits |= 0x04; break; + case 'owe': bits |= 0x08; break; + } + } + + if (bits) + append('transition_disable', sprintf('0x%02x', bits)); +} + function iface_key_caching(config) { if (config.wpa < 2) return; @@ -533,6 +569,8 @@ export function generate(interface, data, config, vlans, stas, phy_features) { iface_mfp(config); + iface_transition_disable(config); + iface_key_caching(config); iface_hs20(config);