@@ -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.");