From ab49b404fd28fdab032ca7607f5b99d24ac33c47 Mon Sep 17 00:00:00 2001 From: James Prestwood Date: Fri, 22 Nov 2024 07:15:49 -0800 Subject: [PATCH] station: support PMKSA connections 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.");