From: Mitchell Augustin <mitchell.augustin@canonical.com>
Date: Fri, 3 Oct 2025 11:32:34 -0500
Subject: core: add handlers for SaePasswordMismatch signal

Trigger a new auth request to the user when the SaePasswordMismatch
signal is received from wpa_supplicant.

Closes #904

Origin: upstream, https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/commit/bcb96a1b19dae7306be0b373f0cf76660499ac61
---
 src/core/devices/wifi/nm-device-wifi.c        | 70 +++++++++++++++++++++++++++
 src/core/supplicant/nm-supplicant-interface.c | 30 ++++++++++++
 src/core/supplicant/nm-supplicant-interface.h |  2 +
 3 files changed, 102 insertions(+)

diff --git a/src/core/devices/wifi/nm-device-wifi.c b/src/core/devices/wifi/nm-device-wifi.c
index 2cd41b2..5e1729b 100644
--- a/src/core/devices/wifi/nm-device-wifi.c
+++ b/src/core/devices/wifi/nm-device-wifi.c
@@ -191,6 +191,12 @@ static void supplicant_iface_notify_p2p_available(NMSupplicantInterface *iface,
                                                   GParamSpec            *pspec,
                                                   NMDeviceWifi          *self);
 
+static void supplicant_iface_notify_wpa_psk_mismatch_cb(NMSupplicantInterface *iface,
+                                                        NMDeviceWifi          *self);
+
+static void supplicant_iface_notify_wpa_sae_mismatch_cb(NMSupplicantInterface *iface,
+                                                        NMDeviceWifi          *self);
+
 static void periodic_update(NMDeviceWifi *self);
 
 static void ap_add_remove(NMDeviceWifi *self,
@@ -621,6 +627,14 @@ supplicant_interface_acquire_cb(NMSupplicantManager         *supplicant_manager,
                      "notify::" NM_SUPPLICANT_INTERFACE_P2P_AVAILABLE,
                      G_CALLBACK(supplicant_iface_notify_p2p_available),
                      self);
+    g_signal_connect(priv->sup_iface,
+                     NM_SUPPLICANT_INTERFACE_PSK_MISMATCH,
+                     G_CALLBACK(supplicant_iface_notify_wpa_psk_mismatch_cb),
+                     self);
+    g_signal_connect(priv->sup_iface,
+                     NM_SUPPLICANT_INTERFACE_SAE_MISMATCH,
+                     G_CALLBACK(supplicant_iface_notify_wpa_sae_mismatch_cb),
+                     self);
 
     _scan_notify_is_scanning(self);
 
@@ -2829,6 +2843,62 @@ handle_auth_or_fail(NMDeviceWifi *self, NMActRequest *req, gboolean new_secrets)
     return TRUE;
 }
 
+static void
+supplicant_iface_notify_wpa_psk_mismatch_cb(NMSupplicantInterface *iface, NMDeviceWifi *self)
+{
+    NMDevice     *device = NM_DEVICE(self);
+    NMActRequest *req;
+    const char   *setting_name = NM_SETTING_WIRELESS_SECURITY_SETTING_NAME;
+
+    if (nm_device_get_state(device) != NM_DEVICE_STATE_CONFIG)
+        return;
+
+    _LOGI(LOGD_DEVICE | LOGD_WIFI,
+          "Activation: (wifi) psk mismatch reported by supplicant, asking for new key");
+
+    req = nm_device_get_act_request(NM_DEVICE(self));
+    g_return_if_fail(req != NULL);
+
+    nm_act_request_clear_secrets(req);
+
+    cleanup_association_attempt(self, TRUE);
+    nm_device_state_changed(device,
+                            NM_DEVICE_STATE_NEED_AUTH,
+                            NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT);
+    wifi_secrets_get_secrets(self,
+                             setting_name,
+                             NM_SECRET_AGENT_GET_SECRETS_FLAG_ALLOW_INTERACTION
+                                 | NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW);
+}
+
+static void
+supplicant_iface_notify_wpa_sae_mismatch_cb(NMSupplicantInterface *iface, NMDeviceWifi *self)
+{
+    NMDevice     *device = NM_DEVICE(self);
+    NMActRequest *req;
+    const char   *setting_name = NM_SETTING_WIRELESS_SECURITY_SETTING_NAME;
+
+    if (nm_device_get_state(device) != NM_DEVICE_STATE_CONFIG)
+        return;
+
+    _LOGI(LOGD_DEVICE | LOGD_WIFI,
+          "Activation: (wifi) SAE password mismatch reported by supplicant, asking for new key");
+
+    req = nm_device_get_act_request(NM_DEVICE(self));
+    g_return_if_fail(req != NULL);
+
+    nm_act_request_clear_secrets(req);
+
+    cleanup_association_attempt(self, TRUE);
+    nm_device_state_changed(device,
+                            NM_DEVICE_STATE_NEED_AUTH,
+                            NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT);
+    wifi_secrets_get_secrets(self,
+                             setting_name,
+                             NM_SECRET_AGENT_GET_SECRETS_FLAG_ALLOW_INTERACTION
+                                 | NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW);
+}
+
 /*
  * supplicant_connection_timeout_cb
  *
diff --git a/src/core/supplicant/nm-supplicant-interface.c b/src/core/supplicant/nm-supplicant-interface.c
index eb7a40a..992dc91 100644
--- a/src/core/supplicant/nm-supplicant-interface.c
+++ b/src/core/supplicant/nm-supplicant-interface.c
@@ -65,6 +65,8 @@ enum {
     WPS_CREDENTIALS, /* WPS credentials received */
     GROUP_STARTED,   /* a new Group (interface) was created */
     GROUP_FINISHED,  /* a Group (interface) has been finished */
+    PSK_MISMATCH,    /* supplicant reported incorrect PSK */
+    SAE_MISMATCH,    /* supplicant reported incorrect SAE Password */
     LAST_SIGNAL
 };
 
@@ -3105,6 +3107,15 @@ _signal_handle(NMSupplicantInterface *self,
             return;
         }
 
+        if (nm_streq(signal_name, "PskMismatch")) {
+            g_signal_emit(self, signals[PSK_MISMATCH], 0);
+            return;
+        }
+
+        if (nm_streq(signal_name, "SaePasswordMismatch")) {
+            g_signal_emit(self, signals[SAE_MISMATCH], 0);
+            return;
+        }
         return;
     }
 
@@ -3737,4 +3748,23 @@ nm_supplicant_interface_class_init(NMSupplicantInterfaceClass *klass)
                                            G_TYPE_NONE,
                                            1,
                                            G_TYPE_STRING);
+
+    signals[PSK_MISMATCH] = g_signal_new(NM_SUPPLICANT_INTERFACE_PSK_MISMATCH,
+                                         G_OBJECT_CLASS_TYPE(object_class),
+                                         G_SIGNAL_RUN_LAST,
+                                         0,
+                                         NULL,
+                                         NULL,
+                                         NULL,
+                                         G_TYPE_NONE,
+                                         0);
+    signals[SAE_MISMATCH] = g_signal_new(NM_SUPPLICANT_INTERFACE_SAE_MISMATCH,
+                                         G_OBJECT_CLASS_TYPE(object_class),
+                                         G_SIGNAL_RUN_LAST,
+                                         0,
+                                         NULL,
+                                         NULL,
+                                         NULL,
+                                         G_TYPE_NONE,
+                                         0);
 }
diff --git a/src/core/supplicant/nm-supplicant-interface.h b/src/core/supplicant/nm-supplicant-interface.h
index b8d9b01..e77f5a4 100644
--- a/src/core/supplicant/nm-supplicant-interface.h
+++ b/src/core/supplicant/nm-supplicant-interface.h
@@ -86,6 +86,8 @@ typedef enum {
 #define NM_SUPPLICANT_INTERFACE_WPS_CREDENTIALS "wps-credentials"
 #define NM_SUPPLICANT_INTERFACE_GROUP_STARTED   "group-started"
 #define NM_SUPPLICANT_INTERFACE_GROUP_FINISHED  "group-finished"
+#define NM_SUPPLICANT_INTERFACE_PSK_MISMATCH    "wpa-psk-mismatch"
+#define NM_SUPPLICANT_INTERFACE_SAE_MISMATCH    "wpa-sae-password-mismatch"
 
 typedef struct _NMSupplicantInterfaceClass NMSupplicantInterfaceClass;
 
