[PATCH v3] Add option to disable SAE key_mgmt without PMF

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Add the `sae_check_mfp` global option to limit SAE when PMF will
not be selected for the connection.
With this option SAE is avoided when the hardware is not capable
of PMF due to missing ciphers.
With this option SAE is avoided on capable hardware when the AP
does not enable PMF.

Allows falling back to PSK on drivers with the
WPA_DRIVER_FLAGS_SAE capability but do not support the BIP cipher
necessary for PMF. This enables configurations that can fall back
to WPA-PSK and avoid problems associating with APs configured
with `sae_require_mfp=1`.

Useful when `pmf=1` and `sae_check_mfp=1` are enabled and networks
are configured with ieee80211w=3 (default) and key_mgmt="WPA-PSK SAE".
In this configuration if the device is unable to use PMF due to
lacking BIP group ciphers it will avoid SAE and fallback to
WPA-PSK for that connection.

Signed-off-by: Jeffery Miller <jefferymiller@xxxxxxxxxx>
---

V1 -> V2: Changed the option to be global instead of per-network.
V2 -> V3: Added WPA3 compliance comment to wpa_supplicant.conf.

 tests/hwsim/test_sae.py            | 63 ++++++++++++++++++++++++++++++
 wpa_supplicant/config.c            |  1 +
 wpa_supplicant/config.h            | 19 +++++++++
 wpa_supplicant/config_file.c       |  3 ++
 wpa_supplicant/sme.c               |  9 ++++-
 wpa_supplicant/wpa_cli.c           |  4 +-
 wpa_supplicant/wpa_supplicant.c    | 14 ++++++-
 wpa_supplicant/wpa_supplicant.conf | 28 +++++++++++++
 wpa_supplicant/wpa_supplicant_i.h  |  6 +++
 9 files changed, 142 insertions(+), 5 deletions(-)

diff --git a/tests/hwsim/test_sae.py b/tests/hwsim/test_sae.py
index 4e5687def..06a301d06 100644
--- a/tests/hwsim/test_sae.py
+++ b/tests/hwsim/test_sae.py
@@ -424,6 +424,69 @@ def test_sae_mixed_mfp(dev, apdev):
     dev[2].connect("test-sae", psk="12345678", ieee80211w="0", scan_freq="2412")
     dev[2].dump_monitor()
 
+def _test_sae_mixed_check_mfp(dev, apdev):
+    """Mixed SAE and non-SAE network with the sae_check_mfp option"""
+    check_sae_capab(dev[0])
+    check_sae_capab(dev[1])
+    params = hostapd.wpa2_params(ssid="test-sae", passphrase="12345678")
+    params['wpa_key_mgmt'] = 'SAE WPA-PSK'
+    params["ieee80211w"] = "1"
+    hostapd.add_ap(apdev[0], params)
+
+    params = hostapd.wpa2_params(ssid="test-sae-no-mfp", passphrase="12345678")
+    params['wpa_key_mgmt'] = 'SAE WPA-PSK'
+    params["ieee80211w"] = "0"
+    hostapd.add_ap(apdev[1], params)
+
+    dev[0].set("sae_check_mfp", "0")
+    dev[0].request("SET sae_groups ")
+    dev[0].connect("test-sae", psk="12345678", key_mgmt="SAE WPA-PSK",
+                   ieee80211w="0", scan_freq="2412")
+    dev[0].dump_monitor()
+    status = dev[0].get_status()
+    if status['key_mgmt'] != "SAE":
+        raise Exception("SAE without sae_check_mfp was not allowed")
+
+    # Confirm SAE is used when sae_check_mfp is not set for non-pmf AP.
+    dev[2].set("sae_check_mfp", "0")
+    dev[2].request("SET sae_groups ")
+    dev[2].connect("test-sae-no-mfp", psk="12345678", key_mgmt="SAE WPA-PSK",
+                   ieee80211w="1", scan_freq="2412")
+    status = dev[2].get_status()
+    if status['key_mgmt'] != "SAE":
+        raise Exception("SAE without sae_check_mfp was not allowed")
+    dev[2].dump_monitor()
+
+    # Confirm SAE is not used with the PMF disabled network configuration.
+    dev[1].set("sae_check_mfp", "1")
+    dev[1].request("SET sae_groups ")
+    dev[1].connect("test-sae", psk="12345678", key_mgmt="SAE WPA-PSK",
+                   ieee80211w="0", scan_freq="2412")
+    status = dev[1].get_status()
+    dev[1].request("DISCONNECT")
+    dev[1].wait_disconnected()
+    dev[1].dump_monitor()
+    if status['key_mgmt'] != "WPA2-PSK":
+        raise Exception("SAE without MFP was allowed")
+
+    # Confirm SAE is not used connecting to PMF disabled AP.
+    dev[1].set("sae_check_mfp", "1")
+    dev[1].request("SET sae_groups ")
+    dev[1].connect("test-sae-no-mfp", psk="12345678", key_mgmt="SAE WPA-PSK",
+                   ieee80211w="1", scan_freq="2412")
+    status = dev[1].get_status()
+    if status['key_mgmt'] != "WPA2-PSK":
+        raise Exception("SAE without MFP was allowed")
+
+def test_sae_mixed_check_mfp(dev, apdev):
+    """Mixed SAE and non-SAE network with the sae_check_mfp option"""
+    try:
+        _test_sae_mixed_check_mfp(dev, apdev)
+    finally:
+        dev[0].set("sae_check_mfp", "0")
+        dev[1].set("sae_check_mfp", "0")
+        dev[2].set("sae_check_mfp", "0")
+
 def test_sae_and_psk_transition_disable(dev, apdev):
     """SAE and PSK transition disable indication"""
     check_sae_capab(dev[0])
diff --git a/wpa_supplicant/config.c b/wpa_supplicant/config.c
index a91c689d0..3972073b3 100644
--- a/wpa_supplicant/config.c
+++ b/wpa_supplicant/config.c
@@ -5279,6 +5279,7 @@ static const struct global_parse_data global_fields[] = {
 	{ INT_RANGE(auto_interworking, 0, 1), 0 },
 	{ INT(okc), 0 },
 	{ INT(pmf), 0 },
+	{ INT_RANGE(sae_check_mfp, 0, 1), 0 },
 	{ FUNC(sae_groups), 0 },
 	{ INT_RANGE(sae_pwe, 0, 3), 0 },
 	{ INT_RANGE(sae_pmkid_in_assoc, 0, 1), 0 },
diff --git a/wpa_supplicant/config.h b/wpa_supplicant/config.h
index 8b8be2a45..cd6c9fa21 100644
--- a/wpa_supplicant/config.h
+++ b/wpa_supplicant/config.h
@@ -1238,6 +1238,25 @@ struct wpa_config {
 	 */
 	enum mfp_options pmf;
 
+	/**
+	 * sae_check_mfp - Whether to limit SAE based on mfp capabilities
+	 *
+	 * With this check SAE key_mgmt will not be selected if PMF is
+	 * not enabled.
+	 * Scenarios where enabling this check will limit SAE:
+	 *  1) ieee8011w=0 is set for the network.
+	 *  2) The AP does not have PMF enabled.
+	 *  3) ieee8011w for the network is the default(3), pmf=1 is enabled
+	 *     globally and the device does not support the BIP cipher.
+	 *
+	 * Useful to allow the BIP cipher check that occurs for ieee80211w=3
+	 * and pmf=1 to also avoid using SAE key_mgmt.
+	 * Useful when hardware does not support BIP to still to allow
+	 * connecting to sae_require_mfp=1 wpa2+wpa3 transition access points
+	 * by automatically selecting PSK instead of SAE.
+	 */
+	int sae_check_mfp;
+
 	/**
 	 * sae_groups - Preference list of enabled groups for SAE
 	 *
diff --git a/wpa_supplicant/config_file.c b/wpa_supplicant/config_file.c
index b637dbfbc..f01f30eb0 100644
--- a/wpa_supplicant/config_file.c
+++ b/wpa_supplicant/config_file.c
@@ -1355,6 +1355,9 @@ static void wpa_config_write_global(FILE *f, struct wpa_config *config)
 	if (config->beacon_int)
 		fprintf(f, "beacon_int=%d\n", config->beacon_int);
 
+	if (config->sae_check_mfp)
+		fprintf(f, "sae_check_mfp=%d\n", config->sae_check_mfp);
+
 	if (config->sae_groups) {
 		int i;
 		fprintf(f, "sae_groups=");
diff --git a/wpa_supplicant/sme.c b/wpa_supplicant/sme.c
index 41b67f8eb..72d035d71 100644
--- a/wpa_supplicant/sme.c
+++ b/wpa_supplicant/sme.c
@@ -413,8 +413,13 @@ static void sme_send_authentication(struct wpa_supplicant *wpa_s,
 #endif /* CONFIG_DPP */
 		} else if (wpa_parse_wpa_ie(rsn, 2 + rsn[1], &ied) == 0 &&
 			   wpa_key_mgmt_sae(ied.key_mgmt)) {
-			wpa_dbg(wpa_s, MSG_DEBUG, "Using SAE auth_alg");
-			params.auth_alg = WPA_AUTH_ALG_SAE;
+			if (wpas_is_sae_avoided(wpa_s, ssid, &ied)) {
+				wpa_dbg(wpa_s, MSG_DEBUG,
+				        "SAE enabled, but disallowing SAE auth_alg without PMF");
+			} else {
+				wpa_dbg(wpa_s, MSG_DEBUG, "Using SAE auth_alg");
+				params.auth_alg = WPA_AUTH_ALG_SAE;
+			}
 		} else {
 			wpa_dbg(wpa_s, MSG_DEBUG,
 				"SAE enabled, but target BSS does not advertise SAE AKM for RSN");
diff --git a/wpa_supplicant/wpa_cli.c b/wpa_supplicant/wpa_cli.c
index 197efe0b7..4ab9a9465 100644
--- a/wpa_supplicant/wpa_cli.c
+++ b/wpa_supplicant/wpa_cli.c
@@ -503,7 +503,7 @@ static char ** wpa_cli_complete_set(const char *str, int pos)
 		"autoscan", "wps_nfc_dev_pw_id", "wps_nfc_dh_pubkey",
 		"wps_nfc_dh_privkey", "wps_nfc_dev_pw", "ext_password_backend",
 		"p2p_go_max_inactivity", "auto_interworking", "okc", "pmf",
-		"sae_groups", "dtim_period", "beacon_int",
+		"sae_check_mfp", "sae_groups", "dtim_period", "beacon_int",
 		"ap_vendor_elements", "ignore_old_scan_res", "freq_list",
 		"scan_cur_freq", "scan_res_valid_for_connect",
 		"sched_scan_interval",
@@ -602,7 +602,7 @@ static char ** wpa_cli_complete_get(const char *str, int pos)
 		"go_venue_group", "go_venue_type",
 		"wps_nfc_dev_pw_id", "ext_password_backend",
 		"p2p_go_max_inactivity", "auto_interworking", "okc", "pmf",
-		"dtim_period", "beacon_int", "ignore_old_scan_res",
+		"sae_check_mfp", "dtim_period", "beacon_int", "ignore_old_scan_res",
 		"scan_cur_freq", "scan_res_valid_for_connect",
 		"sched_scan_interval",
 		"sched_scan_start_delay",
diff --git a/wpa_supplicant/wpa_supplicant.c b/wpa_supplicant/wpa_supplicant.c
index 761017248..c7aa5f662 100644
--- a/wpa_supplicant/wpa_supplicant.c
+++ b/wpa_supplicant/wpa_supplicant.c
@@ -1651,9 +1651,11 @@ int wpa_supplicant_set_suites(struct wpa_supplicant *wpa_s,
 
 	sel = ie.key_mgmt & ssid->key_mgmt;
 #ifdef CONFIG_SAE
-	if (!(wpa_s->drv_flags & WPA_DRIVER_FLAGS_SAE))
+	if (!(wpa_s->drv_flags & WPA_DRIVER_FLAGS_SAE) ||
+	    wpas_is_sae_avoided(wpa_s, ssid, &ie)) {
 		sel &= ~(WPA_KEY_MGMT_SAE | WPA_KEY_MGMT_SAE_EXT_KEY |
 			 WPA_KEY_MGMT_FT_SAE | WPA_KEY_MGMT_FT_SAE_EXT_KEY);
+	}
 #endif /* CONFIG_SAE */
 #ifdef CONFIG_IEEE80211R
 	if (!(wpa_s->drv_flags & (WPA_DRIVER_FLAGS_SME |
@@ -8195,6 +8197,16 @@ int wpas_get_ssid_pmf(struct wpa_supplicant *wpa_s, struct wpa_ssid *ssid)
 	return ssid->ieee80211w;
 }
 
+#ifdef CONFIG_SAE
+int wpas_is_sae_avoided(struct wpa_supplicant *wpa_s,
+			struct wpa_ssid *ssid,
+			const struct wpa_ie_data *ie) {
+	return (wpa_s->conf->sae_check_mfp &&
+		(!(ie->capabilities &
+		   (WPA_CAPABILITY_MFPC | WPA_CAPABILITY_MFPR)) ||
+		 wpas_get_ssid_pmf(wpa_s, ssid) == NO_MGMT_FRAME_PROTECTION));
+}
+#endif /* CONFIG_SAE */
 
 int pmf_in_use(struct wpa_supplicant *wpa_s, const u8 *addr)
 {
diff --git a/wpa_supplicant/wpa_supplicant.conf b/wpa_supplicant/wpa_supplicant.conf
index 90061ba67..cf0b86dd0 100644
--- a/wpa_supplicant/wpa_supplicant.conf
+++ b/wpa_supplicant/wpa_supplicant.conf
@@ -419,6 +419,34 @@ fast_reauth=1
 # RSN.
 #pmf=0
 
+# sae_check_mfp: require mfp support to select SAE key_mgmt
+# 0 = do not check PMF for SAE (default)
+# 1 = limit SAE when PMF is not enabled
+#
+# When enabled SAE will not be selected if PMF will not be used
+# for the connection.
+# Scenarios where this check will limit SAE:
+#  1) ieee80211w=0 is set for the network
+#  2) The AP does not have PMF enabled.
+#  3) ieee80211w is unset, pmf=1 is enabled globally, and
+#     the device does not support the BIP cipher.
+# Consider the configuration of globals sae_check_mfp=1, pmf=1 and a
+# network configured with ieee80211w unset and key_mgmt=SAE WPA-PSK.
+# In the example WPA-PSK will be used if the device does not support
+# the BIP cipher or the AP has pmf disabled.
+# Limiting SAE with this check can avoid failing to associate to an AP
+# that is configured with sae_requires_mfp=1 if the device does
+# not support PMF due to lack of the BIP cipher.
+#
+# Enabling this check helps with compliance of the WPA3
+# specification for WPA3-Personal transition mode.
+# The WPA3 specification section 2.3 "WPA3-Personal transition mode" item 8
+# states "A STA shall negotiate PMF when associating to an AP using SAE".
+# With this check WPA3 capable devices when connecting
+# to transition mode APs that do not advertise PMF support
+# will not use SAE and instead fallback to PSK.
+#sae_check_mfp=0
+
 # Enabled SAE finite cyclic groups in preference order
 # By default (if this parameter is not set), the mandatory group 19 (ECC group
 # defined over a 256-bit prime order field, NIST P-256) is preferred and groups
diff --git a/wpa_supplicant/wpa_supplicant_i.h b/wpa_supplicant/wpa_supplicant_i.h
index 813e5ac1b..89c0a75aa 100644
--- a/wpa_supplicant/wpa_supplicant_i.h
+++ b/wpa_supplicant/wpa_supplicant_i.h
@@ -1844,6 +1844,12 @@ int wpas_get_ssid_pmf(struct wpa_supplicant *wpa_s, struct wpa_ssid *ssid);
 int pmf_in_use(struct wpa_supplicant *wpa_s, const u8 *addr);
 void wpa_s_setup_sae_pt(struct wpa_config *conf, struct wpa_ssid *ssid);
 
+#ifdef CONFIG_SAE
+int wpas_is_sae_avoided(struct wpa_supplicant *wpa_s,
+			struct wpa_ssid *ssid,
+			const struct wpa_ie_data *ie);
+#endif /* CONFIG_SAE */
+
 int wpas_init_ext_pw(struct wpa_supplicant *wpa_s);
 
 void dump_freq_data(struct wpa_supplicant *wpa_s, const char *title,
-- 
2.38.0.135.g90850a2211-goog


_______________________________________________
Hostap mailing list
Hostap@xxxxxxxxxxxxxxxxxxx
http://lists.infradead.org/mailman/listinfo/hostap



[Index of Archives]     [Linux Wireless]     [Linux Kernel]     [ATH6KL]     [Linux Bluetooth]     [Linux Netdev]     [Kernel Newbies]     [IDE]     [Security]     [Git]     [Netfilter]     [Bugtraq]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Linux ATA RAID]     [Samba]     [Device Mapper]

  Powered by Linux