]> git.ipfire.org Git - thirdparty/hostap.git/commitdiff
drivers: RX-only configuration of the next TK during 4-way handshake
authorJouni Malinen <quic_jouni@quicinc.com>
Sat, 8 Feb 2025 18:11:30 +0000 (20:11 +0200)
committerJouni Malinen <j@w1.fi>
Sun, 9 Feb 2025 09:35:35 +0000 (11:35 +0200)
Introduce option for drivers to avoid race conditions with TK
configuration during 4-way handshake. The next pairwise TK is made
available to the driver interface before sending message 3 of the 4-way
handshake on the AP and after having received message 3 (but before
transmitting message 4) on the station. This allows the driver to
configure the next TK as an alternative RX-only key during the race
window and take the new TK fully into use once the 4-way handshake has
been fully completed. The alternative RX-only key must not be used for
TX and if a TK has already been configured, both that previously
configured TK and the next RX-only TK need to be allowed to decrypt
received frames (i.e., both needs to be tried before discarding a frame
as invalid). When taking the new TK fully into use, RX counters for it
must not be cleared. Unencrypted EAPOL frames must be allowed to be
received when only an RX-only TK is configured in the beginning of an
association.

This commit is only introducing the hostapd and wpa_supplicant internal
pieces for this functionality and this does not result in any changes to
the actual driver operations. This enables future commits to extend
driver wrappers (src/drivers/driver_*.c) to take this functionality into
use.

Signed-off-by: Jouni Malinen <quic_jouni@quicinc.com>
15 files changed:
src/ap/wpa_auth.c
src/ap/wpa_auth_glue.c
src/common/defs.h
src/common/wpa_common.h
src/drivers/driver.h
src/drivers/driver_atheros.c
src/drivers/driver_bsd.c
src/drivers/driver_hostap.c
src/drivers/driver_ndis.c
src/drivers/driver_nl80211.c
src/drivers/driver_openbsd.c
src/drivers/driver_privsep.c
src/drivers/driver_wext.c
src/rsn_supp/wpa.c
wpa_supplicant/ibss_rsn.c

index 5ccce3a12771ef742157c07ac058d8c059aba453..1a8bec8e6e05fc4a1cc6fa2a5789030f59349d03 100644 (file)
@@ -4799,6 +4799,20 @@ SM_STATE(WPA_PTK, PTKINITNEGOTIATING)
                        return;
                }
 
+               if (!sm->use_ext_key_id && sm->TimeoutCtr == 1 &&
+                   wpa_auth_set_key(sm->wpa_auth, 0,
+                                    wpa_cipher_to_alg(sm->pairwise),
+                                    sm->addr, 0, sm->PTK.tk,
+                                    wpa_cipher_key_len(sm->pairwise),
+                                    KEY_FLAG_PAIRWISE_NEXT)) {
+                       /* Continue anyway since the many drivers do not support
+                        * configuration of the TK for RX-only purposes for
+                        * cases where multiple keys might be in use in parallel
+                        * and this being an optional optimization to avoid race
+                        * condition during TK changes that could result in some
+                        * protected frames getting discarded. */
+               }
+
 #ifdef CONFIG_PASN
                if (sm->wpa_auth->conf.secure_ltf &&
                    ieee802_11_rsnx_capab(sm->rsnxe,
index 1c26734e729413100b21679c19b35e38ff0ef135..afd849b7eef08a1ab279f8d1dd3b8725472b3f39 100644 (file)
@@ -512,6 +512,7 @@ static int hostapd_wpa_auth_set_key(void *ctx, int vlan_id, enum wpa_alg alg,
 {
        struct hostapd_data *hapd = ctx;
        const char *ifname = hapd->conf->iface;
+       int set_tx = !(key_flag & KEY_FLAG_NEXT);
 
        if (vlan_id > 0) {
                ifname = hostapd_get_vlan_id_ifname(hapd->conf->vlan, vlan_id);
@@ -564,8 +565,8 @@ static int hostapd_wpa_auth_set_key(void *ctx, int vlan_id, enum wpa_alg alg,
                hapd->last_gtk_len = key_len;
        }
 #endif /* CONFIG_TESTING_OPTIONS */
-       return hostapd_drv_set_key(ifname, hapd, alg, addr, idx, vlan_id, 1,
-                                  NULL, 0, key, key_len, key_flag);
+       return hostapd_drv_set_key(ifname, hapd, alg, addr, idx, vlan_id,
+                                  set_tx, NULL, 0, key, key_len, key_flag);
 }
 
 
index 650e66d1ce8ac74ff8ecb0e5bd139e4ee44ac931..85e0f02d1d5b2735bcac2c3dd9786e00b968843a 100644 (file)
@@ -490,6 +490,7 @@ enum key_flag {
        KEY_FLAG_GROUP                  = BIT(4),
        KEY_FLAG_PAIRWISE               = BIT(5),
        KEY_FLAG_PMK                    = BIT(6),
+       KEY_FLAG_NEXT                   = BIT(7),
        /* Used flag combinations */
        KEY_FLAG_RX_TX                  = KEY_FLAG_RX | KEY_FLAG_TX,
        KEY_FLAG_GROUP_RX_TX            = KEY_FLAG_GROUP | KEY_FLAG_RX_TX,
@@ -502,8 +503,10 @@ enum key_flag {
        KEY_FLAG_PAIRWISE_RX            = KEY_FLAG_PAIRWISE | KEY_FLAG_RX,
        KEY_FLAG_PAIRWISE_RX_TX_MODIFY  = KEY_FLAG_PAIRWISE_RX_TX |
                                          KEY_FLAG_MODIFY,
+       KEY_FLAG_PAIRWISE_NEXT          = KEY_FLAG_PAIRWISE_RX | KEY_FLAG_NEXT,
        /* Max allowed flags for each key type */
-       KEY_FLAG_PAIRWISE_MASK          = KEY_FLAG_PAIRWISE_RX_TX_MODIFY,
+       KEY_FLAG_PAIRWISE_MASK          = KEY_FLAG_PAIRWISE_RX_TX_MODIFY |
+                                         KEY_FLAG_NEXT,
        KEY_FLAG_GROUP_MASK             = KEY_FLAG_GROUP_RX_TX_DEFAULT,
        KEY_FLAG_PMK_MASK               = KEY_FLAG_PMK,
 };
index 9f1a539bf597b445d6a9f737dab60fbe357cfdca..e8abe230862b69d136e59e5c9074171060878757 100644 (file)
@@ -271,6 +271,8 @@ struct wpa_ptk {
        size_t ptk_len;
        size_t ltf_keyseed_len;
        int installed; /* 1 if key has already been installed to driver */
+       bool installed_rx; /* whether TK has been installed as the next TK
+                           * for temporary RX-only use in the driver */
 };
 
 struct wpa_gtk {
index f04050003899e9b0bb759a7b21b7257d639c741d..b9d3d0013b03f3c8991c86b3d5bd181b15094373 100644 (file)
@@ -2031,10 +2031,17 @@ struct wpa_driver_set_key_params {
         * %KEY_FLAG_GROUP_TX_DEFAULT
         *  GTK key valid for TX only, immediately taking over TX.
         * %KEY_FLAG_PAIRWISE_RX_TX
-        *  Pairwise key immediately becoming the active pairwise key.
+        *  Pairwise key immediately becoming the active pairwise key. If this
+        *  key was previously set as an alternative RX-only key with
+        *  %KEY_FLAG_PAIRWISE_RX | %KEY_FLAG_NEXT, the alternative RX-only key
+        *  is taken into use for both TX and RX without changing the RX counter
+        *  values.
         * %KEY_FLAG_PAIRWISE_RX
         *  Pairwise key not yet valid for TX. (Only usable when Extended
-        *  Key ID is supported by the driver.)
+        *  Key ID is supported by the driver or when configuring the next TK
+        *  for RX-only with %KEY_FLAG_NEXT in which case the new TK can be used
+        *  as an alternative key for decrypting received frames without
+        *  replacing the possibly already configured old TK.)
         * %KEY_FLAG_PAIRWISE_RX_TX_MODIFY
         *  Enable TX for a pairwise key installed with
         *  KEY_FLAG_PAIRWISE_RX.
@@ -3154,6 +3161,53 @@ struct wpa_driver_ops {
         * broadcast keys, so key index 0 is available for this kind of
         * configuration.
         *
+        * For pairwise keys, there are potential race conditions between
+        * enabling a new TK on each end of the connection and sending the first
+        * protected frame. Drivers have multiple options on which style of key
+        * configuration to support with the simplest option not providing any
+        * protection for the race condition while the more complex options do
+        * provide partial or full protection.
+        *
+        * Option 1: Do not support extended key IDs (i.e., use only Key ID 0
+        * for pairwise keys) and do not support configuration of the next TK
+        * as an alternative RX key. This provides no protection, but is simple
+        * to support. The driver needs to ignore set_key() calls with
+        * KEY_FLAG_NEXT.
+        *
+        * Option 2: Do not support extended key IDs (i.e., use only Key ID 0
+        * for pairwise keys), but support configuration of the next TK as an
+        * alternative RX key for the initial 4-way handshake. This provides
+        * protection for the initial key setup at the beginning of an
+        * association. The driver needs to configure the initial TK for RX-only
+        * when receiving a set_key() call with KEY_FLAG_NEXT. This RX-only key
+        * is ready for receiving protected Data frames from the peer before the
+        * local device has enabled the key for TX. Unprotected EAPOL frames
+        * need to be allowed even when this next TK is configured as RX-only
+        * key. The same key is then set with KEY_FLAG_PAIRWISE_RX_TX to enable
+        * its use for both TX and RX. The driver ignores set_key() calls with
+        * KEY_FLAG_NEXT when a TK has been configured. When fully enabling the
+        * TK for TX and RX, the RX counters associated with the TK must not be
+        * cleared.
+        *
+        * Option 3: Same as option 2, but the driver supports multiple RX keys
+        * in parallel during PTK rekeying. The driver processed set_key() calls
+        * with KEY_FLAG_NEXT also when a TK has been configured. At that point
+        * in the rekeying sequence the driver uses the previously configured TK
+        * for TX and decrypts received frames with either the previously
+        * configured TK or the next TK (RX-only).
+        *
+        * Option 4: The driver supports extended Key IDs and they are used for
+        * an association but does not support KEY_FLAG_NEXT (options 2 and 3).
+        * The next TK is configured as RX-only with KEY_FLAG_PAIRWISE_RX and
+        * it is enabled for TX and RX with KEY_FLAG_PAIRWISE_RX_TX_MODIFY. When
+        * extended key ID is not used for an association, the driver behaves
+        * like in option 1.
+        *
+        * Option 5 and 6: Like option 4 but with support for KEY_FLAG_NEXT as
+        * described above for options 2 and 3, respectively. Option 4 is used
+        * for cases where extended key IDs are used for an association. Option
+        * 2 or 3 is used for cases where extended key IDs are not used.
+        *
         * Please note that TKIP keys include separate TX and RX MIC keys and
         * some drivers may expect them in different order than wpa_supplicant
         * is using. If the TX/RX keys are swapped, all TKIP encrypted packets
index 71863306afadd88243024e101c7219eab5a805f7..47da8669e857977417122994b8fdec73a60f037b 100644 (file)
@@ -504,6 +504,9 @@ atheros_set_key(void *priv, struct wpa_driver_set_key_params *params)
        const u8 *key = params->key;
        size_t key_len = params->key_len;
 
+       if (params->key_flag & KEY_FLAG_NEXT)
+               return -1;
+
        if (alg == WPA_ALG_NONE)
                return atheros_del_key(drv, addr, key_idx);
 
index 0979fc5dddc531f9ed3f6d0ffdf2f139ed77710d..66155b41c051e67edada34ff21d38e95c399eee5 100644 (file)
@@ -325,6 +325,9 @@ bsd_set_key(void *priv, struct wpa_driver_set_key_params *params)
        const u8 *key = params->key;
        size_t key_len = params->key_len;
 
+       if (params->key_flag & KEY_FLAG_NEXT)
+               return -1;
+
        wpa_printf(MSG_DEBUG, "%s: alg=%d addr=%p key_idx=%d set_tx=%d "
                   "seq_len=%zu key_len=%zu", __func__, alg, addr, key_idx,
                   set_tx, seq_len, key_len);
index 3aa5860bc433eacaccd002bcc549e09ec924ff96..74c7767babdc8738ddd53d3b3c8667a7fe44fde7 100644 (file)
@@ -411,6 +411,9 @@ static int wpa_driver_hostap_set_key(void *priv,
        const u8 *key = params->key;
        size_t key_len = params->key_len;
 
+       if (params->key_flag & KEY_FLAG_NEXT)
+               return -1;
+
        blen = sizeof(*param) + key_len;
        buf = os_zalloc(blen);
        if (buf == NULL)
index 0351705229e4aeb9bd4b70aaebeb787d90db9379..b030b0bcf61a5f60eabaaa42caa69fed7e310dce 100644 (file)
@@ -1037,6 +1037,9 @@ static int
 wpa_driver_ndis_set_key_wrapper(void *priv,
                                struct wpa_driver_set_key_params *params)
 {
+       if (params->key_flag & KEY_FLAG_NEXT)
+               return -1;
+
        return wpa_driver_ndis_set_key(params->ifname, priv,
                                       params->alg, params->addr,
                                       params->key_idx, params->set_tx,
index 85b00af91b60c8b2fadf15fa4943790ecbfa9fb9..c7272756400403411ccda35ba501a47156c3f06b 100644 (file)
@@ -3572,6 +3572,14 @@ static int wpa_driver_nl80211_set_key(struct i802_bss *bss,
                return 0;
        }
 
+       if (key_flag & KEY_FLAG_NEXT) {
+               /* For now, ignore these since this needs support from the
+                * driver to handle the special cases of two active RX keys. */
+               wpa_printf(MSG_DEBUG,
+                          "nl80211: set_key for the next TK for RX-only - ignored");
+               return -EOPNOTSUPP;
+       }
+
        ret = -ENOBUFS;
        key_msg = nlmsg_alloc();
        if (!key_msg)
index bfc231178a2f2ef5a625beb95cc183e019d26141..dac312a0cdd50e20486c236dd23f25e23808ba12 100644 (file)
@@ -77,6 +77,9 @@ wpa_driver_openbsd_set_key(void *priv, struct wpa_driver_set_key_params *params)
        const u8 *key = params->key;
        size_t key_len = params->key_len;
 
+       if (params->key_flag & KEY_FLAG_NEXT)
+               return -1;
+
        if (key_len > IEEE80211_PMK_LEN ||
            (key_flag & KEY_FLAG_PMK_MASK) != KEY_FLAG_PMK) {
                return -1;
index d6735b49c4af98449ecf2ce1ad8a94fc446e58fc..d7c6b01a317e30a154f8e3acb81086eaa48b9114 100644 (file)
@@ -219,6 +219,9 @@ static int wpa_driver_privsep_set_key(void *priv,
        const u8 *key = params->key;
        size_t key_len = params->key_len;
 
+       if (params->key_flag & KEY_FLAG_NEXT)
+               return -1;
+
        wpa_printf(MSG_DEBUG, "%s: priv=%p alg=%d key_idx=%d set_tx=%d",
                   __func__, priv, alg, key_idx, set_tx);
 
index 2c656fb6fcfa6a41a2da39968f7d8746ce22d4b4..c34c13b804e549c5a3fb5b03ff17fd7da766cf13 100644 (file)
@@ -1833,6 +1833,9 @@ static int wpa_driver_wext_set_key(void *priv,
        const u8 *key = params->key;
        size_t key_len = params->key_len;
 
+       if (params->key_flag & KEY_FLAG_NEXT)
+               return -1;
+
        wpa_printf(MSG_DEBUG, "%s: alg=%d key_idx=%d set_tx=%d seq_len=%lu "
                   "key_len=%lu",
                   __FUNCTION__, alg, key_idx, set_tx,
index 48924418b199102224ce63d3f733d0e10869be46..0d8473841b313b74c4a9ee50e8a921fe4f6491d5 100644 (file)
@@ -1227,14 +1227,16 @@ static int wpa_supplicant_install_ptk(struct wpa_sm *sm,
        enum wpa_alg alg;
        const u8 *key_rsc;
 
-       if (sm->ptk.installed) {
+       if (sm->ptk.installed ||
+           (sm->ptk.installed_rx && (key_flag & KEY_FLAG_NEXT))) {
                wpa_dbg(sm->ctx->msg_ctx, MSG_DEBUG,
                        "WPA: Do not re-install same PTK to the driver");
                return 0;
        }
 
        wpa_dbg(sm->ctx->msg_ctx, MSG_DEBUG,
-               "WPA: Installing PTK to the driver");
+               "WPA: Installing %sTK to the driver",
+               (key_flag & KEY_FLAG_NEXT) ? "next " : "");
 
        if (sm->pairwise_cipher == WPA_CIPHER_NONE) {
                wpa_dbg(sm->ctx->msg_ctx, MSG_DEBUG, "WPA: Pairwise Cipher "
@@ -1268,6 +1270,9 @@ static int wpa_supplicant_install_ptk(struct wpa_sm *sm,
        if (wpa_sm_set_key(sm, -1, alg, wpa_sm_get_auth_addr(sm),
                           sm->keyidx_active, 1, key_rsc, rsclen, sm->ptk.tk,
                           keylen, KEY_FLAG_PAIRWISE | key_flag) < 0) {
+               if (key_flag & KEY_FLAG_NEXT)
+                       return 0; /* Not all drivers support this, so do not
+                                  * report failures on the RX-only set_key */
                wpa_msg(sm->ctx->msg_ctx, MSG_WARNING,
                        "WPA: Failed to set PTK to the driver (alg=%d keylen=%d auth_addr="
                        MACSTR " idx=%d key_flag=0x%x)",
@@ -1293,11 +1298,15 @@ static int wpa_supplicant_install_ptk(struct wpa_sm *sm,
        wpa_sm_store_ptk(sm, sm->bssid, sm->pairwise_cipher,
                         sm->dot11RSNAConfigPMKLifetime, &sm->ptk);
 
-       /* TK is not needed anymore in supplicant */
-       os_memset(sm->ptk.tk, 0, WPA_TK_MAX_LEN);
-       sm->ptk.tk_len = 0;
-       sm->ptk.installed = 1;
-       sm->tk_set = true;
+       if (key_flag & KEY_FLAG_NEXT) {
+               sm->ptk.installed_rx = true;
+       } else {
+               /* TK is not needed anymore in supplicant */
+               os_memset(sm->ptk.tk, 0, WPA_TK_MAX_LEN);
+               sm->ptk.tk_len = 0;
+               sm->ptk.installed = 1;
+               sm->tk_set = true;
+       }
 
        if (sm->wpa_ptk_rekey) {
                eloop_cancel_timeout(wpa_sm_rekey_ptk, sm, NULL);
@@ -2899,6 +2908,16 @@ static void wpa_supplicant_process_3_of_4(struct wpa_sm *sm,
            wpa_supplicant_install_ptk(sm, key, KEY_FLAG_RX))
                goto failed;
 
+       if (!sm->use_ext_key_id &&
+           wpa_supplicant_install_ptk(sm, key, KEY_FLAG_RX | KEY_FLAG_NEXT)) {
+               /* Continue anyway since the many drivers do not support
+                * configuration of the TK for RX-only purposes for cases where
+                * multiple keys might be in use in parallel and this being an
+                * optional optimization to avoid race condition during TK
+                * changes that could result in some protected frames getting
+                * discarded. */
+       }
+
        if (wpa_supplicant_send_4_of_4(sm, wpa_sm_get_auth_addr(sm), key, ver,
                                       key_info, &sm->ptk) < 0)
                goto failed;
index 25039a0f98c5529f3ce166c690f8bc6ab3570589..37eb58726262673009a85d38d4bcb026c68317f8 100644 (file)
@@ -150,6 +150,12 @@ static int supp_set_key(void *ctx, int link_id, enum wpa_alg alg,
 {
        struct ibss_rsn_peer *peer = ctx;
 
+       if (key_flag & KEY_FLAG_NEXT) {
+               wpa_printf(MSG_DEBUG,
+                          "SUPP: Ignore set_key with KEY_FLAG_NEXT");
+               return 0;
+       }
+
        wpa_printf(MSG_DEBUG, "SUPP: %s(alg=%d addr=" MACSTR " key_idx=%d "
                   "set_tx=%d)",
                   __func__, alg, MAC2STR(addr), key_idx, set_tx);
@@ -320,6 +326,12 @@ static int auth_set_key(void *ctx, int vlan_id, enum wpa_alg alg,
        struct ibss_rsn *ibss_rsn = ctx;
        u8 seq[6];
 
+       if (key_flag & KEY_FLAG_NEXT) {
+               wpa_printf(MSG_DEBUG,
+                          "AUTH: Ignore set_key with KEY_FLAG_NEXT");
+               return 0;
+       }
+
        os_memset(seq, 0, sizeof(seq));
 
        if (addr) {