[PATCH 13/15] station: support PMKSA connections

James Prestwood <[email protected]>
Newsgroups dev.linux.lists.iwd
Message-ID <[email protected]>
The actual connection piece of this is very minimal, and only
requires station to check if there is a PMKSA cached, and if so
include the PMKID in the RSNE. Netdev then takes care of the rest.

The remainder of this patch is the error handling if a PMKSA
connection fails with INVALID_PMKID. In this case IWD should retry
the same BSS without PMKSA.

An option was also added to disable PMKSA if a user wants to do
that. In theory PMKSA is actually less secure compared to SAE so
it could be something a user wants to disable. Going forward though
it will be enabled by default as its a requirement from the WiFi
alliance for WPA3 certification.
---
 src/station.c | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 53 insertions(+), 1 deletion(-)

diff --git a/src/station.c b/src/station.c
index 09193eed..5403c332 100644
--- a/src/station.c
+++ b/src/station.c
@@ -63,6 +63,7 @@
 #include "src/eap.h"
 #include "src/eap-tls-common.h"
 #include "src/storage.h"
+#include "src/pmksa.h"
 
 #define STATION_RECENT_NETWORK_LIMIT	5
 #define STATION_RECENT_FREQS_LIMIT	5
@@ -78,6 +79,7 @@ static bool supports_drop_gratuitous_arp;
 static bool supports_drop_unsolicited_na;
 static bool supports_ipv4_drop_unicast_in_l2_multicast;
 static bool supports_ipv6_drop_unicast_in_l2_multicast;
+static bool pmksa_disabled;
 static struct watchlist event_watches;
 static uint32_t known_networks_watch;
 static uint32_t allowed_bands;
@@ -1157,6 +1159,7 @@ static int station_build_handshake_rsn(struct handshake_state *hs,
 					struct network *network,
 					struct scan_bss *bss)
 {
+	struct netdev *netdev = netdev_find(hs->ifindex);
 	const struct l_settings *settings = iwd_get_config();
 	enum security security = network_get_security(network);
 	bool add_mde = false;
@@ -1167,6 +1170,7 @@ static int station_build_handshake_rsn(struct handshake_state *hs,
 	uint8_t *ap_ie;
 	bool disable_ocv;
 	enum band_freq band;
+	struct pmksa *pmksa;
 
 	memset(&info, 0, sizeof(info));
 
@@ -1300,6 +1304,17 @@ build_ie:
 			IE_CIPHER_IS_GCMP_CCMP(info.pairwise_ciphers))
 		info.extended_key_id = true;
 
+	if (IE_AKM_IS_SAE(info.akm_suites) && !pmksa_disabled) {
+		pmksa = pmksa_cache_get(netdev_get_address(netdev), bss->addr,
+					bss->ssid, bss->ssid_len,
+					info.akm_suites);
+		if (pmksa) {
+			handshake_state_set_pmksa(hs, pmksa);
+			info.num_pmkids = 1;
+			info.pmkids = hs->pmksa->pmkid;
+		}
+	}
+
 	/* RSN takes priority */
 	if (bss->rsne) {
 		ap_ie = bss->rsne;
@@ -3391,6 +3406,39 @@ try_next:
 	return station_try_next_bss(station);
 }
 
+static bool station_pmksa_fallback(struct station *station, uint16_t status)
+{
+	/*
+	 * IEEE 802.11-2020 12.6.10.3 Cached PMKSAs and RSNA key management
+	 *
+	 * "If the Authenticator does not have a PMKSA for the PMKIDs in the
+	 * (re)association request or the AKM does not match, its behavior
+	 * depends on how the PMKSA was established. If SAE authentication was
+	 * used to establish the PMKSA, then the AP shall reject (re)association
+	 * by sending a (Re)Association Response frame with status code
+	 * STATUS_INVALID_PMKID. Note that this allows the non-AP STA to fall
+	 * back to full SAE authentication to establish another PMKSA"
+	 */
+	if (status != MMPDU_STATUS_CODE_INVALID_PMKID)
+		return false;
+
+	if (L_WARN_ON(!station->hs))
+		return false;
+
+	if (!IE_AKM_IS_SAE(station->hs->akm_suite) || !station->hs->have_pmksa)
+		return false;
+
+	/*
+	 * Remove the PMKSA from the handshake and return true to re-try the
+	 * same BSS without PMKSA.
+	 */
+	handshake_state_remove_pmksa(station->hs);
+
+	station_debug_event(station, "pmksa-invalid-pmkid");
+
+	return true;
+}
+
 /* A bit more concise for trying to fit these into 80 characters */
 #define IS_TEMPORARY_STATUS(code) \
 	((code) == MMPDU_STATUS_CODE_DENIED_UNSUFFICIENT_BANDWIDTH || \
@@ -3414,7 +3462,7 @@ static bool station_retry_with_status(struct station *station,
 	if (IS_TEMPORARY_STATUS(status_code))
 		network_blacklist_add(station->connected_network,
 						station->connected_bss);
-	else
+	else if (!station_pmksa_fallback(station, status_code))
 		blacklist_add_bss(station->connected_bss->addr);
 
 	iwd_notice(IWD_NOTICE_CONNECT_FAILED, "status: %u", status_code);
@@ -5830,6 +5878,10 @@ static int station_init(void)
 				&anqp_disabled))
 		anqp_disabled = true;
 
+	if (!l_settings_get_bool(iwd_get_config(), "General", "DisablePMKSA",
+				&pmksa_disabled))
+		pmksa_disabled = false;
+
 	if (!netconfig_enabled())
 		l_info("station: Network configuration is disabled.");
 
-- 
2.34.1
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.