[PATCH] ap: add vendor quirk for reused nonce in handshake

berend <[email protected]> Wed, 24 Dec 2025 10:28:40 +0000
Newsgroups dev.linux.lists.iwd
Message-ID <[email protected]>
commit 486cd7b1cd20bfa81437d89cdd7c8f8df7e23bfa
Author: Berend De Schouwer <[email protected]>
Date:   Wed Dec 24 11:28:17 2025 +0200

    ap: add vendor quirk for reused nonce in handshake
    
    Add a vendor quirk when IWD is an AP, to allow some
    Garmin IoT devices to connect.
    
    handshake_reuse_nonce:
      When a supplicant does a handshake, the nonce in message 4 should
      be set to zero.  On some devices it's left identical to message
2.

diff --git a/src/ap.c b/src/ap.c
index af6a1709..1f805a81 100644
--- a/src/ap.c
+++ b/src/ap.c
@@ -59,6 +59,7 @@
 #include "src/diagnostic.h"
 #include "src/band.h"
 #include "src/common.h"
+#include "src/vendor_quirks.h"
 
 struct ap_state {
        struct netdev *netdev;
@@ -143,6 +144,7 @@ struct sta_state {
        struct l_dhcp_lease *ip_alloc_lease;
        bool ip_alloc_sent;
        uint64_t rekey_time;
+       struct vendor_quirk vendor_quirks;
 
        bool ht_support : 1;
        bool ht_greenfield : 1;
@@ -1384,6 +1386,7 @@ static void ap_start_handshake(struct sta_state
*sta, bool use_eapol_start,
        handshake_state_set_ssid(sta->hs, (void *) ap->ssid, strlen(ap-
>ssid));
        handshake_state_set_authenticator_address(sta->hs, own_addr);
        handshake_state_set_supplicant_address(sta->hs, sta->addr);
+       handshake_state_set_vendor_quirks(sta->hs, sta->vendor_quirks);
 
        ap_set_rsn_info(ap, &rsn);
        /*
@@ -2974,6 +2977,7 @@ static void ap_handle_new_station(struct ap_state
*ap, struct l_genl_msg *msg)
        const void *data;
        const uint8_t *mac = NULL;
        uint8_t *assoc_rsne = NULL;
+       const char *vendor_quirks;
 
        if (!l_genl_attr_init(&attr, msg))
                return;
@@ -3014,6 +3018,13 @@ static void ap_handle_new_station(struct
ap_state *ap, struct l_genl_msg *msg)
        sta->assoc_rsne = assoc_rsne;
        sta->aid = ++ap->last_aid;
 
+       vendor_quirks_append_for_mac(sta->addr, &sta->vendor_quirks);
+
+       vendor_quirks = vendor_quirks_to_string(sta->vendor_quirks);
+       if (vendor_quirks)
+               l_debug("vendor quirks for "MAC": %s",
+                               MAC_STR(sta->addr), vendor_quirks);
+
        sta->associated = true;
 
        if (!ap->sta_states)
diff --git a/src/eapol.c b/src/eapol.c
index ab77746f..b603ae39 100644
--- a/src/eapol.c
+++ b/src/eapol.c
@@ -605,7 +605,8 @@ bool eapol_verify_ptk_3_of_4(const struct eapol_key
*ek, bool is_wpa,
        return true;
 }
 
-bool eapol_verify_ptk_4_of_4(const struct eapol_key *ek, bool is_wpa)
+bool eapol_verify_ptk_4_of_4(const struct eapol_key *ek, bool is_wpa,
+                               const uint8_t *quirk_nonce)
 {
        uint16_t key_len;
 
@@ -636,7 +637,9 @@ bool eapol_verify_ptk_4_of_4(const struct eapol_key
*ek, bool is_wpa)
        key_len = L_BE16_TO_CPU(ek->key_length);
        L_WARN_ON(key_len != 0);
 
-       VERIFY_IS_ZERO(ek->key_nonce);
+       if ((!quirk_nonce) || memcmp(ek->key_nonce, quirk_nonce,
+                                       sizeof(ek->key_nonce)))
+               VERIFY_IS_ZERO(ek->key_nonce);
        VERIFY_IS_ZERO(ek->eapol_key_iv);
        VERIFY_IS_ZERO(ek->key_rsc);
        VERIFY_IS_ZERO(ek->reserved);
@@ -2122,10 +2125,15 @@ static void eapol_handle_ptk_4_of_4(struct
eapol_sm *sm,
                                        const struct eapol_key *ek)
 {
        const uint8_t *kck;
+       const uint8_t *quirk_nonce = NULL;
 
        l_debug("ifindex=%u", sm->handshake->ifindex);
 
-       if (!eapol_verify_ptk_4_of_4(ek, false))
+       if (sm->handshake->vendor_quirks.handshake_nonce_reuse &&
+                       sm->handshake->have_snonce)
+               quirk_nonce = sm->handshake->snonce;
+
+       if (!eapol_verify_ptk_4_of_4(ek, false, quirk_nonce))
                return;
 
        if (L_BE64_TO_CPU(ek->key_replay_counter) != sm-
>replay_counter)
diff --git a/src/eapol.h b/src/eapol.h
index 43dc224d..36946017 100644
--- a/src/eapol.h
+++ b/src/eapol.h
@@ -70,7 +70,8 @@ bool eapol_verify_ptk_1_of_4(const struct eapol_key
*ek, size_t mic_len,
 bool eapol_verify_ptk_2_of_4(const struct eapol_key *ek, bool
ptk_complete);
 bool eapol_verify_ptk_3_of_4(const struct eapol_key *ek, bool is_wpa,
                                size_t mic_len);
-bool eapol_verify_ptk_4_of_4(const struct eapol_key *ek, bool is_wpa);
+bool eapol_verify_ptk_4_of_4(const struct eapol_key *ek, bool is_wpa,
+                               const uint8_t *quirk_nonce);
 bool eapol_verify_gtk_1_of_2(const struct eapol_key *ek, bool is_wpa,
                                size_t mic_len);
 bool eapol_verify_gtk_2_of_2(const struct eapol_key *ek, bool is_wpa);
diff --git a/src/vendor_quirks.c b/src/vendor_quirks.c
index 4fba0c33..001fcb13 100644
--- a/src/vendor_quirks.c
+++ b/src/vendor_quirks.c
@@ -47,6 +47,17 @@ static const struct {
        },
 };
 
+static const struct {
+       uint8_t mac[3];
+       struct vendor_quirk quirks;
+} mac_quirk_db[] = {
+       {
+               /* Garmin International */
+               { 0x90, 0xf1, 0x57 },
+               { .handshake_nonce_reuse = true },
+       },
+};
+
 void vendor_quirks_append_for_oui(const uint8_t *oui,
                                        struct vendor_quirk *quirks)
 {
@@ -65,6 +76,22 @@ void vendor_quirks_append_for_oui(const uint8_t
*oui,
        }
 }
 
+void vendor_quirks_append_for_mac(const uint8_t *mac,
+                                       struct vendor_quirk *quirks)
+{
+       size_t i;
+
+       for (i = 0; i < L_ARRAY_SIZE(mac_quirk_db); i++) {
+               const struct vendor_quirk *quirk =
&mac_quirk_db[i].quirks;
+
+               if (memcmp(mac_quirk_db[i].mac, mac, 3))
+                       continue;
+
+               quirks->handshake_nonce_reuse |=
+                               quirk->handshake_nonce_reuse;
+       }
+}
+
 const char *vendor_quirks_to_string(struct vendor_quirk quirks)
 {
        static char out[1024];
@@ -77,6 +104,9 @@ const char *vendor_quirks_to_string(struct
vendor_quirk quirks)
        if (quirks.replay_counter_mismatch)
                s += snprintf(pos, sizeof(out) - s,
"ReplayCounterMismatch");
 
+       if (quirks.handshake_nonce_reuse)
+               s += snprintf(pos, sizeof(out) - s,
"HandshakeNonceReuse");
+
        if (!s)
                return NULL;
 
diff --git a/src/vendor_quirks.h b/src/vendor_quirks.h
index b5d726ca..0e199c3a 100644
--- a/src/vendor_quirks.h
+++ b/src/vendor_quirks.h
@@ -29,11 +29,15 @@
 struct vendor_quirk {
        bool ignore_bss_tm_candidates : 1;
        bool replay_counter_mismatch : 1;
+       bool handshake_nonce_reuse : 1;
 };
 
 void vendor_quirks_append_for_oui(const uint8_t *oui,
                                        struct vendor_quirk *quirks);
 
+void vendor_quirks_append_for_mac(const uint8_t *oui,
+                                       struct vendor_quirk *quirks);
+
 const char *vendor_quirks_to_string(struct vendor_quirk quirks);
 
 #endif /* __IWD_VENDOR_QUIRKS_H */
publickey - Berend De Schouwer - 0x128CD671.asc (application/pgp-keys, 3.1 KB)
-----BEGIN PGP PUBLIC KEY BLOCK-----
Comment: https://gopenpgp.org
Version: GopenPGP 2.8.2

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
i4ZPyDBc3n68rqs=
=9SXl
-----END PGP PUBLIC KEY BLOCK-----
signature.asc (application/pgp-signature, 928 B)
-----BEGIN PGP SIGNATURE-----
Version: ProtonMail

wsGpBAEBCABdBYJpS8BXCZDbbuBxk4Cg6TUUAAAAAAAcABBzYWx0QG5vdGF0
aW9ucy5vcGVucGdwanMub3JnClxNvSea8Ztxt9NaU71bwhYhBBKM1nGLSON6
q6UFAttu4HGTgKDpAADaQRAAtwGWgQiGlE8PoJKnR/GKoRorEXMXDspZgPQs
TFmGqS+ndFaVNxPtOsGglhaOv8bKcpzuxCLvx4GhxpZIcbUvdL/33yomyESJ
8puzExVIyUPStxz5xEUL9OB0DN1ah5Ssxus8O33Xm3Wrz+8S3jblonoLMHAF
WVpISxh1fvrVe0wj9vBGMYMmAHrxzN5pz9vhMf4xlfYeR0F+yR7+TYMqWJCF
JqbDWaUMjkh9xSNskVK/9Fo0kR3ylLAWdpdypXBUo8LkKE7z04Fyu+DHugkK
REV/+BbscT8uH1KeBVkLzg7DyHPqVcpUgU8qm8dl1ymYvrBFeziY0JzKU4v1
Bg6bT2JfewE+giCoeiT+YVuICAGyyOg09rIE43GZRlW22Ve1cn/jNwIWcaJD
oa5rnDVGwgGhA6jcY+K3uy00n+NCZKU/qWClpxUbEoCrbZ0eM2oS+4yfhPyT
1I0VgvY+8HZ5FS/U1kG/N+vmTvKfqFn2kH7Ezd9sClr1wJiV5xzGLCXIzGk0
s0yQ+ek1tQ7Ks67BSuRLQDqC5boyERh4GtghJM1LRAbYBmf4okuYH3ByCG6A
7P1+cV31nhZFl1BmT10H/AFI/jvOFZHA93A7fy6VP8vU97bTtmJYG/t7P0Cu
nh69pFDsvkX9MqD2fuVESv5sUAHb8+ibmBZ8tiB1bS3XFxk=
=1mnd
-----END PGP SIGNATURE-----