According to the IEEE Std 802.11-2024 specification (Table 9-70), the Message Integrity Code (MIC) element must be the last element in the authentication frame body and is present only in certain authentication frames, as defined in Table 9-71. For Enhanced Privacy Protection Key Exchange (EPPKE) authentication, as specified in "IEEE P802.11bi/D3.0, 12.16.9", the MIC is mandatory per Table 9-71. In the SME-supplicant case, userspace constructs the authentication frame body, computes the MIC over that body, appends the MIC element, and passes it to cfg80211 via NL80211_ATTR_AUTH_DATA with NL80211_CMD_AUTHENTICATE. In MLO connections, userspace constructs most of the authentication frame body, excluding the MLE, which mac80211 appends later in ieee80211_send_assoc(). If userspace computes the MIC before this, it produces an incorrect value over an incomplete frame body. Moreover, the MIC element must be the last element in the frame to comply with the specification. Since mac80211 appends the MLE after the userspace-calculated MIC, the ordering is violated. Add support for MIC generation as specified in specification "IEEE Std802.11-2024, 12.13.9.2" and append the MIC element at the end of the frame body for the EPPKE authentication protocol. Signed-off-by: Kavita Kavita --- net/mac80211/ieee80211_i.h | 4 + net/mac80211/mlme.c | 9 ++ net/mac80211/sta_info.h | 9 ++ net/mac80211/util.c | 278 +++++++++++++++++++++++++++++++++++++ 4 files changed, 300 insertions(+) diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h index 50fd5e83ed6d..76682933f4fa 100644 --- a/net/mac80211/ieee80211_i.h +++ b/net/mac80211/ieee80211_i.h @@ -423,6 +423,8 @@ struct ieee80211_mgd_auth_data { u8 key[WLAN_KEY_LEN_WEP104]; u8 key_len, key_idx; + u8 kck[WLAN_MAX_KEY_LEN]; + u8 kck_len; bool done, waiting; bool peer_confirmed; bool timeout_started; @@ -430,6 +432,8 @@ struct ieee80211_mgd_auth_data { u8 ap_addr[ETH_ALEN] __aligned(2); + u32 hash_alg; + u16 trans, status; size_t data_len; u8 data[]; diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c index cc6bc58b0f1f..73e7f76e92ef 100644 --- a/net/mac80211/mlme.c +++ b/net/mac80211/mlme.c @@ -9279,6 +9279,15 @@ int ieee80211_mgd_auth(struct ieee80211_sub_if_data *sdata, memcpy(auth_data->key, req->key, req->key_len); } + if (req->auth_type == NL80211_AUTHTYPE_EPPKE && req->kck && + req->kck_len) { + auth_data->kck_len = req->kck_len; + memcpy(auth_data->kck, req->kck, req->kck_len); + } + + if (req->auth_type == NL80211_AUTHTYPE_EPPKE && auth_data->trans == 1) + auth_data->hash_alg = req->hash_alg; + ieee80211_parse_cfg_selectors(auth_data->userspace_selectors, req->supported_selectors, req->supported_selectors_len); diff --git a/net/mac80211/sta_info.h b/net/mac80211/sta_info.h index 5288d5286651..a5891fa8f146 100644 --- a/net/mac80211/sta_info.h +++ b/net/mac80211/sta_info.h @@ -620,6 +620,8 @@ struct ieee80211_sta_removed_link_stats { } pertid_stats; }; +#define SHA512_DIGEST_LEN 64 + /** * struct sta_info - STA information * @@ -680,6 +682,10 @@ struct ieee80211_sta_removed_link_stats { * * @fast_tx: TX fastpath information * @fast_rx: RX fastpath information + * @f1_hash: The HASH of body of the first EPPKE Authentication frame. Used + * only for non-AP STA. + * @hash_alg: (u32) Hash algorithm used to drive @f1_ hash and used for MIC + * computation in EPPKE Authentication. Used only for non-AP STA. * @tdls_chandef: a TDLS peer can have a wider chandef that is compatible to * the BSS one. * @frags: fragment cache @@ -716,6 +722,9 @@ struct sta_info { struct ieee80211_fast_tx __rcu *fast_tx; struct ieee80211_fast_rx __rcu *fast_rx; + u8 f1_hash[SHA512_DIGEST_LEN]; + u32 hash_alg; + #ifdef CONFIG_MAC80211_MESH struct mesh_sta *mesh; #endif diff --git a/net/mac80211/util.c b/net/mac80211/util.c index 0c46009a3d63..b1aba0395d12 100644 --- a/net/mac80211/util.c +++ b/net/mac80211/util.c @@ -25,6 +25,7 @@ #include #include #include +#include #include "ieee80211_i.h" #include "driver-ops.h" @@ -1073,6 +1074,240 @@ void ieee80211_set_wmm_default(struct ieee80211_link_data *link, } } +static const char *ieee80211_crypto_alg(u32 alg, bool hmac) +{ + switch (alg) { + case NL80211_HASH_ALG_SHA256: + return hmac ? "hmac(sha256)" : "sha256"; + case NL80211_HASH_ALG_SHA384: + return hmac ? "hmac(sha384)" : "sha384"; + case NL80211_HASH_ALG_SHA512: + return hmac ? "hmac(sha512)" : "sha512"; + default: + return NULL; + } +} + +static size_t ieee80211_hash_digest_len(u32 alg) +{ + switch (alg) { + case NL80211_HASH_ALG_SHA256: + return 32; + case NL80211_HASH_ALG_SHA384: + return 48; + case NL80211_HASH_ALG_SHA512: + return 64; + default: + return 0; + } +} + +static size_t ieee80211_mic_len(u32 alg) +{ + return ieee80211_hash_digest_len(alg) / 2; +} + +static int ieee80211_compute_hash(const char *alg, const u8 *data, + size_t data_len, u8 *hash) +{ + struct crypto_shash *tfm; + struct shash_desc *desc; + int ret; + + /* + * Compute a hash over the input buffer. + * + * Steps: + * - crypto_alloc_shash: Allocate a transform handle for the "alg" + * algorithm. + * - crypto_shash_init: Initialize a shash descriptor. + * - crypto_shash_update: Feed input data into the hash context. + * - crypto_shash_final: Finalize the hash and store the digest. + * + * The calls are chained: if any step fails, the subsequent ones are + * skipped and the error code from the failing call is returned. + */ + tfm = crypto_alloc_shash(alg, 0, 0); + if (IS_ERR(tfm)) + return PTR_ERR(tfm); + + desc = kzalloc(sizeof(*desc) + crypto_shash_descsize(tfm), GFP_KERNEL); + if (!desc) { + crypto_free_shash(tfm); + return -ENOMEM; + } + desc->tfm = tfm; + + ret = crypto_shash_init(desc); + if (ret) + goto free; + + ret = crypto_shash_update(desc, data, data_len); + if (ret) + goto free; + + ret = crypto_shash_final(desc, hash); +free: + kfree(desc); + crypto_free_shash(tfm); + return ret; +} + +static int calc_f1_hash(struct sk_buff *skb, struct ieee80211_mgmt *mgmt, + struct sta_info *sta) +{ + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)mgmt; + int hdrlen = ieee80211_hdrlen(hdr->frame_control); + const u8 *body = (const u8 *)mgmt + hdrlen; + size_t body_len = skb->len - hdrlen; + const char *hash_alg; + + hash_alg = ieee80211_crypto_alg(sta->hash_alg, false); + if (!hash_alg) + return -EINVAL; + + return ieee80211_compute_hash(hash_alg, body, body_len, sta->f1_hash); +} + +static int ieee80211_hmac_hash(const char *alg, const u8 *key, size_t key_len, + const u8 **fields, const size_t *len, + size_t num_elem, u8 *out) +{ + struct crypto_shash *tfm; + struct shash_desc *desc; + int ret, i; + + /* + * Compute an HMAC-HASH over multiple input fields. + * + * Steps: + * - crypto_alloc_shash: Allocate a handle for HMAC-SHA256 transform. + * - crypto_shash_setkey: Configure the transform with the provided key. + * - crypto_shash_init: Initialize the hashing context. + * - crypto_shash_update: Feed input buffer into the HMAC state in + * sequence. + * - crypto_shash_final: Finalize the hash and store the digest. + * + * If any step fails, execution jumps to "free", skipping the remaining + * operations. The error code from the failing call is returned. + */ + tfm = crypto_alloc_shash(alg, 0, 0); + if (IS_ERR(tfm)) + return PTR_ERR(tfm); + + ret = crypto_shash_setkey(tfm, key, key_len); + if (ret) + goto free_tfm; + + desc = kzalloc(sizeof(*desc) + crypto_shash_descsize(tfm), GFP_KERNEL); + if (!desc) { + crypto_free_shash(tfm); + return -ENOMEM; + } + desc->tfm = tfm; + + ret = crypto_shash_init(desc); + if (ret) + goto free; + + for (i = 0; i < num_elem; i++) { + ret = crypto_shash_update(desc, fields[i], len[i]); + if (ret) + goto free; + } + ret = crypto_shash_final(desc, out); +free: + kfree(desc); + +free_tfm: + crypto_free_shash(tfm); + return ret; +} + +static int add_mic_element(struct ieee80211_sub_if_data *sdata, + struct sk_buff *skb, + struct ieee80211_mgmt *mgmt, + struct sta_info *sta, size_t mic_len) +{ + struct ieee80211_if_managed *ifmgd = &sdata->u.mgd; + struct ieee80211_mgd_auth_data *auth_data = ifmgd->auth_data; + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)mgmt; + int hdrlen = ieee80211_hdrlen(hdr->frame_control); + u8 *frame_body; + size_t body_len; + const u8 *spa = sdata->vif.addr; + const u8 *bssid = mgmt->da; + const u8 *f1_hash = sta->f1_hash; + const char *alg; + size_t digest_len; + const u8 *fields[4]; + size_t len[4]; + u8 mic_elem[2 + 24]; + u8 mic_buf[SHA512_DIGEST_SIZE]; + int n = 0, ret; + size_t mic_ie_offset; + + if (!mic_len || mic_len > 32) + return -EINVAL; + + if (skb_tailroom(skb) < (2 + mic_len)) + return -ENOMEM; + + alg = ieee80211_crypto_alg(sta->hash_alg, true); + if (!alg) + return -EINVAL; + + digest_len = ieee80211_hash_digest_len(sta->hash_alg); + if (!digest_len) + return -EINVAL; + /* + * Append and compute the MIC element for the Authentication frame. + * + * The MIC as specified in "IEEE 802.11-2024, 12.13.9.2" is + * constructed as follows: + * + * MIC = first MMM octets of + * HMAC-HASH(KCK, SPA || BSSID || F1-Auth || Frame-Data) + * + * - HASH is the algorithm used in 12.13.8. + * - KCK is the Key Confirmation Key. + * - SPA is the MAC address of the non-AP STA (transmitter of the + * first Auth frame) + * - BSSID is the BSSID for the AP’s BSS. + * - F1-Auth is the HASH of the body of the 1st Auth frame. + * - Frame-Data is the body of the Auth frame, including the MIC + * element with its MIC field set to all zeros during computation. + * - MMM is half the digest length of the hash function + * (16 for SHA-256, 24 for SHA-384 etc). + */ + mic_elem[0] = WLAN_EID_MIC; + mic_elem[1] = mic_len; + memset(&mic_elem[2], 0, mic_len); + skb_put_data(skb, mic_elem, 2 + mic_len); + + frame_body = ((u8 *)mgmt) + hdrlen; + body_len = skb->len - hdrlen; + + fields[n] = spa; + len[n++] = ETH_ALEN; + fields[n] = bssid; + len[n++] = ETH_ALEN; + fields[n] = f1_hash; + len[n++] = digest_len; + fields[n] = frame_body; + len[n++] = body_len; + + ret = ieee80211_hmac_hash(alg, auth_data->kck, auth_data->kck_len, + fields, len, n, mic_buf); + if (ret) + return ret; + + mic_ie_offset = skb->len - (2 + mic_len); + memcpy(skb->data + mic_ie_offset + 2, mic_buf, mic_len); + + return 0; +} + void ieee80211_send_auth(struct ieee80211_sub_if_data *sdata, u16 transaction, u16 auth_alg, u16 status, const u8 *extra, size_t extra_len, const u8 *da, @@ -1123,6 +1358,49 @@ void ieee80211_send_auth(struct ieee80211_sub_if_data *sdata, if (multi_link) skb_put_data(skb, &mle, sizeof(mle)); + if (auth_alg == WLAN_AUTH_EPPKE && transaction == 1) { + struct ieee80211_if_managed *ifmgd = &sdata->u.mgd; + struct ieee80211_mgd_auth_data *auth_data = ifmgd->auth_data; + struct sta_info *sta; + + sta = sta_info_get_bss(sdata, mgmt->da); + if (!sta) { + kfree_skb(skb); + return; + } + + spin_lock_bh(&sta->lock); + sta->hash_alg = auth_data->hash_alg; + err = calc_f1_hash(skb, mgmt, sta); + spin_unlock_bh(&sta->lock); + + if (WARN_ON(err)) { + kfree_skb(skb); + return; + } + } + + if (auth_alg == WLAN_AUTH_EPPKE && transaction == 3) { + struct sta_info *sta; + size_t mic_len; + + sta = sta_info_get_bss(sdata, mgmt->da); + if (!sta) { + kfree_skb(skb); + return; + } + + spin_lock_bh(&sta->lock); + mic_len = ieee80211_mic_len(sta->hash_alg); + err = add_mic_element(sdata, skb, mgmt, sta, mic_len); + spin_unlock_bh(&sta->lock); + + if (WARN_ON(err)) { + kfree_skb(skb); + return; + } + } + if (auth_alg == WLAN_AUTH_SHARED_KEY && transaction == 3) { mgmt->frame_control |= cpu_to_le16(IEEE80211_FCTL_PROTECTED); err = ieee80211_wep_encrypt(local, skb, key, key_len, key_idx); -- 2.34.1