diff --git a/hostapd/config_file.c b/hostapd/config_file.c index a37d86ad5..9ce604f49 100644 --- a/hostapd/config_file.c +++ b/hostapd/config_file.c @@ -4271,6 +4271,8 @@ static int hostapd_config_fill(struct hostapd_config *conf, bss->oci_freq_override_wnm_sleep = atoi(pos); } else if (os_strcmp(buf, "eap_skip_prot_success") == 0) { bss->eap_skip_prot_success = atoi(pos); + } else if (os_strcmp(buf, "delay_eapol_tx") == 0) { + conf->delay_eapol_tx = atoi(pos); #endif /* CONFIG_TESTING_OPTIONS */ #ifdef CONFIG_SAE } else if (os_strcmp(buf, "sae_password") == 0) { diff --git a/hostapd/hostapd.conf b/hostapd/hostapd.conf index 427a49e0c..c3c03bba9 100644 --- a/hostapd/hostapd.conf +++ b/hostapd/hostapd.conf @@ -3086,6 +3086,11 @@ own_ip_addr=127.0.0.1 # Include only ECSA IE without CSA IE where possible # (channel switch operating class is needed) #ecsa_ie_only=0 +# +# Delay EAPOL-Key messages 1/4 and 3/4 by not sending the frame until the last +# attempt (wpa_pairwise_update_count). This will trigger a timeout on all +# previous attempts and thus delays the frame. (testing only) +#delay_eapol_tx=0 ##### Multiple BSSID support ################################################## # diff --git a/src/ap/ap_config.h b/src/ap/ap_config.h index 98ed81a98..4e14bc6f2 100644 --- a/src/ap/ap_config.h +++ b/src/ap/ap_config.h @@ -1076,6 +1076,7 @@ struct hostapd_config { double ignore_reassoc_probability; double corrupt_gtk_rekey_mic_probability; int ecsa_ie_only; + bool delay_eapol_tx; #endif /* CONFIG_TESTING_OPTIONS */ #ifdef CONFIG_ACS diff --git a/src/ap/wpa_auth.c b/src/ap/wpa_auth.c index e3dfd8a6a..d9acf101d 100644 --- a/src/ap/wpa_auth.c +++ b/src/ap/wpa_auth.c @@ -1736,10 +1736,25 @@ static void wpa_send_eapol(struct wpa_authenticator *wpa_auth, if (!sm) return; + ctr = pairwise ? sm->TimeoutCtr : sm->GTimeoutCtr; + +#ifdef CONFIG_TESTING_OPTIONS + /* When delay_eapol_tx is true, delay the EAPOL-Key transmission by + * sending it only on the last attempt after all timeouts for the prior + * skipped attemps. */ + if (wpa_auth->conf.delay_eapol_tx && + ctr != wpa_auth->conf.wpa_pairwise_update_count) { + wpa_msg(sm->wpa_auth->conf.msg_ctx, MSG_INFO, + "DELAY-EAPOL-TX-%d", ctr); + goto skip_tx; + } +#endif /* CONFIG_TESTING_OPTIONS */ __wpa_send_eapol(wpa_auth, sm, key_info, key_rsc, nonce, kde, kde_len, keyidx, encr, 0); +#ifdef CONFIG_TESTING_OPTIONS +skip_tx: +#endif /* CONFIG_TESTING_OPTIONS */ - ctr = pairwise ? sm->TimeoutCtr : sm->GTimeoutCtr; if (ctr == 1 && wpa_auth->conf.tx_status) timeout_ms = pairwise ? eapol_key_timeout_first : eapol_key_timeout_first_group; diff --git a/src/ap/wpa_auth.h b/src/ap/wpa_auth.h index d95b2567e..eed016acc 100644 --- a/src/ap/wpa_auth.h +++ b/src/ap/wpa_auth.h @@ -240,6 +240,7 @@ struct wpa_auth_config { unsigned int gtk_rsc_override_set:1; unsigned int igtk_rsc_override_set:1; int ft_rsnxe_used; + bool delay_eapol_tx; #endif /* CONFIG_TESTING_OPTIONS */ unsigned int oci_freq_override_eapol_m3; unsigned int oci_freq_override_eapol_g1; diff --git a/src/ap/wpa_auth_glue.c b/src/ap/wpa_auth_glue.c index 250d5a158..a87d2f389 100644 --- a/src/ap/wpa_auth_glue.c +++ b/src/ap/wpa_auth_glue.c @@ -117,6 +117,7 @@ static void hostapd_wpa_auth_conf(struct hostapd_bss_config *conf, #ifdef CONFIG_TESTING_OPTIONS wconf->corrupt_gtk_rekey_mic_probability = iconf->corrupt_gtk_rekey_mic_probability; + wconf->delay_eapol_tx = iconf->delay_eapol_tx; if (conf->own_ie_override && wpabuf_len(conf->own_ie_override) <= MAX_OWN_IE_OVERRIDE) { wconf->own_ie_override_len = wpabuf_len(conf->own_ie_override); diff --git a/tests/hwsim/test_ap_pmf.py b/tests/hwsim/test_ap_pmf.py index d83df5d74..2486a90d7 100644 --- a/tests/hwsim/test_ap_pmf.py +++ b/tests/hwsim/test_ap_pmf.py @@ -1470,3 +1470,47 @@ def test_ap_pmf_sta_global_require2(dev, apdev): raise Exception("Unexpected connection") finally: dev[0].set("pmf", "0") + +def test_ap_pmf_drop_robust_mgmt_prior_to_keys_installation(dev, apdev): + """Drop non protected Robust Action frames prior to keys installation""" + ssid = "test-pmf-required" + passphrase = '12345678' + params = hostapd.wpa2_params(ssid=ssid, passphrase=passphrase) + params['delay_eapol_tx'] = '1' + params['ieee80211w'] = '2' + params['wpa_pairwise_update_count'] = '5' + hapd = hostapd.add_ap(apdev[0], params, wait_enabled=False) + + # Spectrum management with Channel Switch element + msg = {'fc': 0x00d0, + 'sa': hapd.own_addr(), + 'da': dev[0].own_addr(), + 'bssid': hapd.own_addr(), + 'payload': binascii.unhexlify('00042503000608') + } + + dev[0].connect(ssid, psk=passphrase, scan_freq='2412', ieee80211w='1', + wait_connect=False) + + # wait for the first delay before sending the frame + ev = hapd.wait_event(['DELAY-EAPOL-TX-1'], timeout=10) + if ev is None: + raise Exception("EAPOL is not delayed") + + # send the Action frame while connecting (prior to keys installation) + hapd.mgmt_tx(msg) + + dev[0].wait_connected(timeout=10, error="Timeout on connection") + hapd.wait_sta() + hwsim_utils.test_connectivity(dev[0], hapd) + + # Verify no channel switch event + ev = dev[0].wait_event(['CTRL-EVENT-STARTED-CHANNEL-SWITCH'], timeout=5) + if ev is not None: + raise Exception("Unexpected CSA prior to keys installation") + + # Send the frame after keys installation and verify channel switch event + hapd.mgmt_tx(msg) + ev = dev[0].wait_event(['CTRL-EVENT-STARTED-CHANNEL-SWITCH'], timeout=5) + if ev is None: + raise Exception("Expected CSA handling after keys installation")