[PATCH] AP: Allow the default group management cipher to be written in RSNE
Kyle Crenshaw
b1nc0d3x at gmail.com
Sat Oct 10 12:55:37 PDT 2026
Since commit 44fa5e747b7a ("FT: Remove optional fields from RSNE when
using PMF"), hostapd leaves the PMKID Count and Group Management Cipher
Suite fields out of the RSNE when PMF is enabled and the cipher is the
default AES-128-CMAC. That is valid, but some drivers do not transmit
the RSNE from hostapd in Beacon and Probe Response frames. Their
firmware builds the element itself and always includes these two
fields. One example is Broadcom FullMAC (BCM43602, firmware
7.35.177.61) in AP mode.
The RSNE in EAPOL-Key msg 3/4 then comes from hostapd and is six octets
shorter than the one in the Beacon frame. Stations that compare the two
octet by octet, such as wpa_supplicant, reject the 4-way handshake with
reason 17 (IE in 4-way handshake differs). The same failure is reported
for brcmfmac in AP mode with ieee80211w=1 at
https://github.com/raspberrypi/linux/issues/3619.
Add explicit_group_mgmt_cipher=1. With it, the PMKID Count and Group
Management Cipher Suite fields are written whenever PMF is enabled, so
the msg 3/4 RSNE matches what such firmware advertises. This restores
the encoding hostapd used before v2.5, including for FT. Commit
44fa5e747b7a removed it partly to work around stations that rejected
msg 3/4 during FT initial mobility domain association when these
fields were present, so the default (0) keeps the current shorter
RSNE. This also brings back the AES-128-CMAC case that commit
2c0e21ef27c6 removed as unreachable.
This is a configuration parameter rather than something derived from
the driver, because a generic driver interface such as driver_bsd
cannot tell whether the firmware below it builds its own RSNE.
Tested with a BCM43602 AP on FreeBSD (driver_bsd) and a PMF-required
PSK-SHA256 station. With the parameter set, the 4-way handshake
completes and data passes. Without it, the station disconnects with
reason 17. The new hwsim test cases check the RSNE encoding with and
without the parameter and an FT+PMF exchange with it.
Signed-off-by: Kyle Crenshaw <b1nc0d3x at gmail.com>
---
hostapd/config_file.c | 2 ++
hostapd/hostapd.conf | 14 ++++++++++++++
src/ap/ap_config.h | 1 +
src/ap/wpa_auth.h | 1 +
src/ap/wpa_auth_glue.c | 1 +
src/ap/wpa_auth_ie.c | 26 +++++++++++++++++++-------
tests/hwsim/test_ap_ft.py | 11 ++++++++++-
tests/hwsim/test_ap_pmf.py | 32 ++++++++++++++++++++++++++++++++
8 files changed, 80 insertions(+), 8 deletions(-)
diff --git a/hostapd/config_file.c b/hostapd/config_file.c
index 2717adc55..9e41800b8 100644
--- a/hostapd/config_file.c
+++ b/hostapd/config_file.c
@@ -3675,6 +3675,8 @@ static int hostapd_config_fill(struct hostapd_config *conf,
line, pos);
return 1;
}
+ } else if (os_strcmp(buf, "explicit_group_mgmt_cipher") == 0) {
+ bss->explicit_group_mgmt_cipher = !!atoi(pos);
} else if (os_strcmp(buf, "beacon_prot") == 0) {
bss->beacon_prot = atoi(pos);
} else if (os_strcmp(buf, "assoc_sa_query_max_timeout") == 0) {
diff --git a/hostapd/hostapd.conf b/hostapd/hostapd.conf
index 8f902b753..1e5766c52 100644
--- a/hostapd/hostapd.conf
+++ b/hostapd/hostapd.conf
@@ -2139,6 +2139,20 @@ own_ip_addr=127.0.0.1
# available in deployed devices.
#group_mgmt_cipher=AES-128-CMAC
+# Include the Group Management Cipher Suite (and the PMKID Count before it)
+# in the RSNE even when it is the default AES-128-CMAC. Both forms are valid.
+# Some drivers transmit an RSNE that their firmware builds itself in Beacon
+# and Probe Response frames, and that RSNE always includes these fields.
+# Stations that compare the RSNE in EAPOL-Key msg 3/4 octet by octet with
+# the one in the Beacon frame then reject the 4-way handshake (reason 17).
+# Set this to 1 for such drivers. This applies to the RSNE that hostapd
+# builds (including the one it uses for Beacon and Probe Response frames, FT,
+# and the RSN Override elements) and restores the encoding hostapd used
+# before v2.5.
+# 0 = omit the default cipher suite (default)
+# 1 = always include it when management frame protection is enabled
+#explicit_group_mgmt_cipher=0
+
# Beacon Protection (management frame protection for Beacon frames)
# This depends on management frame protection being enabled (ieee80211w != 0)
# and beacon protection support indication from the driver.
diff --git a/src/ap/ap_config.h b/src/ap/ap_config.h
index e3dcbfd82..303f64408 100644
--- a/src/ap/ap_config.h
+++ b/src/ap/ap_config.h
@@ -372,6 +372,7 @@ struct hostapd_bss_config {
enum mfp_options rsn_override_mfp;
enum mfp_options rsn_override_mfp_2;
int group_mgmt_cipher;
+ bool explicit_group_mgmt_cipher;
int beacon_prot;
/* dot11AssociationSAQueryMaximumTimeout (in TUs) */
unsigned int assoc_sa_query_max_timeout;
diff --git a/src/ap/wpa_auth.h b/src/ap/wpa_auth.h
index b1b6de09b..dcdf9675a 100644
--- a/src/ap/wpa_auth.h
+++ b/src/ap/wpa_auth.h
@@ -200,6 +200,7 @@ struct wpa_auth_config {
enum mfp_options rsn_override_mfp_2;
int beacon_prot;
int group_mgmt_cipher;
+ bool explicit_group_mgmt_cipher;
int sae_require_mfp;
#ifdef CONFIG_OCV
int ocv; /* Operating Channel Validation */
diff --git a/src/ap/wpa_auth_glue.c b/src/ap/wpa_auth_glue.c
index 0fbfc4726..eed5eacf0 100644
--- a/src/ap/wpa_auth_glue.c
+++ b/src/ap/wpa_auth_glue.c
@@ -163,6 +163,7 @@ static void hostapd_wpa_auth_conf(struct hostapd_iface *iface,
wconf->rsn_override_mfp_2 = conf->rsn_override_mfp_2;
wconf->beacon_prot = conf->beacon_prot;
wconf->group_mgmt_cipher = conf->group_mgmt_cipher;
+ wconf->explicit_group_mgmt_cipher = conf->explicit_group_mgmt_cipher;
wconf->sae_require_mfp = conf->sae_require_mfp;
wconf->ssid_protection = conf->ssid_protection;
wconf->ssid_len = conf->ssid.ssid_len;
diff --git a/src/ap/wpa_auth_ie.c b/src/ap/wpa_auth_ie.c
index 3606564a2..1c0650ede 100644
--- a/src/ap/wpa_auth_ie.c
+++ b/src/ap/wpa_auth_ie.c
@@ -123,7 +123,7 @@ static u16 wpa_own_rsn_capab(struct wpa_auth_config *conf,
static u8 * rsne_write_data(u8 *buf, size_t len, u8 *pos, int group,
int pairwise, int key_mgmt, u16 rsn_capab,
const u8 *pmkid, enum mfp_options mfp,
- int group_mgmt_cipher)
+ int group_mgmt_cipher, bool explicit_group_mgmt)
{
int num_suites, res;
u8 *count;
@@ -349,7 +349,8 @@ static u8 * rsne_write_data(u8 *buf, size_t len, u8 *pos, int group,
if (mfp != NO_MGMT_FRAME_PROTECTION &&
- group_mgmt_cipher != WPA_CIPHER_AES_128_CMAC) {
+ (group_mgmt_cipher != WPA_CIPHER_AES_128_CMAC ||
+ explicit_group_mgmt)) {
if (2 + 4 > buf + len - pos)
return NULL;
if (!pmkid) {
@@ -360,6 +361,9 @@ static u8 * rsne_write_data(u8 *buf, size_t len, u8 *pos, int group,
/* Management Group Cipher Suite */
switch (group_mgmt_cipher) {
+ case WPA_CIPHER_AES_128_CMAC:
+ RSN_SELECTOR_PUT(pos, RSN_CIPHER_SUITE_AES_128_CMAC);
+ break;
case WPA_CIPHER_BIP_GMAC_128:
RSN_SELECTOR_PUT(pos, RSN_CIPHER_SUITE_BIP_GMAC_128);
break;
@@ -418,7 +422,8 @@ int wpa_write_rsn_ie(struct wpa_auth_config *conf, u8 *buf, size_t len,
pos = rsne_write_data(buf, len, pos, conf->wpa_group,
conf->rsn_pairwise, conf->wpa_key_mgmt,
wpa_own_rsn_capab(conf, conf->ieee80211w), pmkid,
- conf->ieee80211w, conf->group_mgmt_cipher);
+ conf->ieee80211w, conf->group_mgmt_cipher,
+ conf->explicit_group_mgmt_cipher);
if (!pos)
return -1;
@@ -448,7 +453,8 @@ static int wpa_write_rsne_override(struct wpa_auth_config *conf, u8 *buf,
conf->rsn_override_key_mgmt,
wpa_own_rsn_capab(conf, conf->rsn_override_mfp),
NULL, conf->rsn_override_mfp,
- conf->group_mgmt_cipher);
+ conf->group_mgmt_cipher,
+ conf->explicit_group_mgmt_cipher);
if (!pos)
return -1;
@@ -478,7 +484,8 @@ static int wpa_write_rsne_override_2(struct wpa_auth_config *conf, u8 *buf,
conf->rsn_override_key_mgmt_2,
wpa_own_rsn_capab(conf, conf->rsn_override_mfp_2),
NULL, conf->rsn_override_mfp_2,
- conf->group_mgmt_cipher);
+ conf->group_mgmt_cipher,
+ conf->explicit_group_mgmt_cipher);
if (!pos)
return -1;
@@ -506,7 +513,8 @@ int wpa_write_802_1x_rsne(struct wpa_authenticator *wpa_auth, u8 *buf,
pos = rsne_write_data(buf, len, pos, group_cipher,
pairwise_cipher, akmp,
wpa_own_rsn_capab(conf, conf->ieee80211w),
- pmkid, mfp, group_mgmt_cipher);
+ pmkid, mfp, group_mgmt_cipher,
+ conf->explicit_group_mgmt_cipher);
if (!pos)
return -1;
@@ -540,9 +548,13 @@ int wpa_write_eppke_rsne(const u8 *wpa_ie, size_t wpa_ie_len,
"EPPKE: No valid AP RSNE as a starting point");
return -1;
}
+ /*
+ * This RSNE is not compared octet by octet against the RSNE in Beacon
+ * frames, so there is no need to follow explicit_group_mgmt_cipher.
+ */
pos = rsne_write_data(buf, len, pos, data.group_cipher, pairwise_cipher,
akmp, data.capabilities, pmkid, mfp,
- data.mgmt_group_cipher);
+ data.mgmt_group_cipher, false);
if (!pos)
return -1;
diff --git a/tests/hwsim/test_ap_ft.py b/tests/hwsim/test_ap_ft.py
index 29bec3de9..c31b314f1 100644
--- a/tests/hwsim/test_ap_ft.py
+++ b/tests/hwsim/test_ap_ft.py
@@ -565,6 +565,10 @@ def test_ap_ft_pmf_over_ds(dev, apdev):
"""WPA2-PSK-FT AP with PMF (over DS)"""
run_ap_ft_pmf(dev, apdev, "1", over_ds=True)
+def test_ap_ft_pmf_explicit_group_mgmt_cipher(dev, apdev):
+ """WPA2-PSK-FT AP with PMF and explicit_group_mgmt_cipher=1"""
+ run_ap_ft_pmf(dev, apdev, "2", explicit_group_mgmt=True)
+
def test_ap_ft_pmf_required(dev, apdev):
"""WPA2-PSK-FT AP with PMF required on STA"""
run_ap_ft_pmf(dev, apdev, "2")
@@ -580,7 +584,8 @@ def test_ap_ft_pmf_beacon_prot(dev, apdev):
if ev is not None:
raise Exception("Beacon loss detected")
-def run_ap_ft_pmf(dev, apdev, ieee80211w, over_ds=False, beacon_prot=False):
+def run_ap_ft_pmf(dev, apdev, ieee80211w, over_ds=False, beacon_prot=False,
+ explicit_group_mgmt=False):
ssid = "test-ft"
passphrase = "12345678"
@@ -588,11 +593,15 @@ def run_ap_ft_pmf(dev, apdev, ieee80211w, over_ds=False, beacon_prot=False):
params["ieee80211w"] = "2"
if beacon_prot:
params["beacon_prot"] = "1"
+ if explicit_group_mgmt:
+ params["explicit_group_mgmt_cipher"] = "1"
hapd0 = hostapd.add_ap(apdev[0], params)
params = ft_params2(ssid=ssid, passphrase=passphrase)
params["ieee80211w"] = "2"
if beacon_prot:
params["beacon_prot"] = "1"
+ if explicit_group_mgmt:
+ params["explicit_group_mgmt_cipher"] = "1"
hapd1 = hostapd.add_ap(apdev[1], params)
Wlantest.setup(hapd0)
diff --git a/tests/hwsim/test_ap_pmf.py b/tests/hwsim/test_ap_pmf.py
index 06e93d4d2..0152d8509 100644
--- a/tests/hwsim/test_ap_pmf.py
+++ b/tests/hwsim/test_ap_pmf.py
@@ -157,6 +157,38 @@ def test_ocv_sa_query_csa_missing(dev, apdev):
if ev is None:
raise Exception("No disconnection event received from hostapd")
+def run_ap_pmf_explicit_group_mgmt_cipher(dev, apdev, explicit):
+ ssid = "test-pmf-explicit"
+ params = hostapd.wpa2_params(ssid=ssid, passphrase="12345678")
+ params["wpa_key_mgmt"] = "WPA-PSK-SHA256"
+ params["ieee80211w"] = "2"
+ if explicit is not None:
+ params["explicit_group_mgmt_cipher"] = explicit
+ hapd = hostapd.add_ap(apdev[0], params)
+ dev[0].connect(ssid, psk="12345678", ieee80211w="2",
+ key_mgmt="WPA-PSK-SHA256", proto="WPA2", scan_freq="2412")
+ hwsim_utils.test_connectivity(dev[0], hapd)
+ bss = dev[0].get_bss(apdev[0]['bssid'])
+ rsne = parse_ie(bss['ie'])[48]
+ # PMKID Count 0 followed by Group Management Cipher Suite 00-0f-ac-6
+ tail = binascii.unhexlify("0000000fac06")
+ hex_rsne = binascii.hexlify(rsne).decode()
+ if explicit == "1":
+ if not rsne.endswith(tail):
+ raise Exception("No Group Management Cipher Suite in RSNE: " +
+ hex_rsne)
+ elif rsne.endswith(tail):
+ raise Exception("Unexpected Group Management Cipher Suite in RSNE: " +
+ hex_rsne)
+
+def test_ap_pmf_explicit_group_mgmt_cipher(dev, apdev):
+ """PMF AP with explicit_group_mgmt_cipher=1"""
+ run_ap_pmf_explicit_group_mgmt_cipher(dev, apdev, "1")
+
+def test_ap_pmf_default_group_mgmt_cipher_omitted(dev, apdev):
+ """PMF AP omits the default Group Management Cipher Suite"""
+ run_ap_pmf_explicit_group_mgmt_cipher(dev, apdev, None)
+
@remote_compatible
def test_ap_pmf_optional(dev, apdev):
"""WPA2-PSK AP with PMF optional"""
--
2.54.0
More information about the Hostap
mailing list