AP: MLO: Handle Multi-Link element during authentication

In case the AP is an AP MLD, parse the Multi-Link element from the
Authentication frame, store the relevant information, and prepare the
response Multi-Link element.

If the AP is not an AP MLD or the parsing of the element fails, continue
the authentication flow without MLD support.

For SAE, it is needed to skip various fixed fields in
the Authentication frame. Implement it for SAE with H2E.

TODO: This should be extended to other authentication algorithms which
are allowed for MLD connections and have fixed fields in the
Authentication frames, according to IEEE P802.11-REVme/D3.0, Table 9-69
(Presence of fields and elements in Authentications frames).

This commit doesn't support FILS, FT, etc.

Signed-off-by: Ilan Peer <ilan.peer@intel.com>
Signed-off-by: Andrei Otcheretianski <andrei.otcheretianski@intel.com>
This commit is contained in:
Andrei Otcheretianski 2023-05-22 22:33:42 +03:00 committed by Jouni Malinen
parent f540d078c9
commit bcbe80a66a
3 changed files with 319 additions and 14 deletions

View file

@ -396,17 +396,38 @@ static int send_auth_reply(struct hostapd_data *hapd, struct sta_info *sta,
u8 *buf;
size_t rlen;
int reply_res = WLAN_STATUS_UNSPECIFIED_FAILURE;
const u8 *sa = hapd->own_addr;
struct wpabuf *ml_resp = NULL;
#ifdef CONFIG_IEEE80211BE
/*
* Once a non-AP MLD is added to the driver, the addressing should use
* the MLD MAC address. Thus, use the MLD address instead of translating
* the addresses.
*/
if (hapd->conf->mld_ap && sta && sta->mld_info.mld_sta) {
sa = hapd->mld_addr;
ml_resp = hostapd_ml_auth_resp(hapd);
if (!ml_resp)
return -1;
}
#endif /* CONFIG_IEEE80211BE */
rlen = IEEE80211_HDRLEN + sizeof(reply->u.auth) + ies_len;
if (ml_resp)
rlen += wpabuf_len(ml_resp);
buf = os_zalloc(rlen);
if (buf == NULL)
if (!buf) {
wpabuf_free(ml_resp);
return -1;
}
reply = (struct ieee80211_mgmt *) buf;
reply->frame_control = IEEE80211_FC(WLAN_FC_TYPE_MGMT,
WLAN_FC_STYPE_AUTH);
os_memcpy(reply->da, dst, ETH_ALEN);
os_memcpy(reply->sa, hapd->own_addr, ETH_ALEN);
os_memcpy(reply->sa, sa, ETH_ALEN);
os_memcpy(reply->bssid, bssid, ETH_ALEN);
reply->u.auth.auth_alg = host_to_le16(auth_alg);
@ -416,6 +437,14 @@ static int send_auth_reply(struct hostapd_data *hapd, struct sta_info *sta,
if (ies && ies_len)
os_memcpy(reply->u.auth.variable, ies, ies_len);
#ifdef CONFIG_IEEE80211BE
if (ml_resp)
os_memcpy(reply->u.auth.variable + ies_len,
wpabuf_head(ml_resp), wpabuf_len(ml_resp));
wpabuf_free(ml_resp);
#endif /* CONFIG_IEEE80211BE */
wpa_printf(MSG_DEBUG, "authentication reply: STA=" MACSTR
" auth_alg=%d auth_transaction=%d resp=%d (IE len=%lu) (dbg=%s)",
MAC2STR(dst), auth_alg, auth_transaction,
@ -2748,6 +2777,8 @@ static void handle_auth(struct hostapd_data *hapd,
size_t resp_ies_len = 0;
u16 seq_ctrl;
struct radius_sta rad_info;
const u8 *dst, *sa, *bssid;
bool mld_sta = false;
if (len < IEEE80211_HDRLEN + sizeof(mgmt->u.auth)) {
wpa_printf(MSG_INFO, "handle_auth - too short payload (len=%lu)",
@ -2765,6 +2796,20 @@ static void handle_auth(struct hostapd_data *hapd,
}
#endif /* CONFIG_TESTING_OPTIONS */
sa = mgmt->sa;
#ifdef CONFIG_IEEE80211BE
/*
* Handle MLO authentication before the station is added to hostapd and
* the driver so that the station MLD MAC address would be used in both
* hostapd and the driver.
*/
sa = hostapd_process_ml_auth(hapd, mgmt, len);
if (sa)
mld_sta = true;
else
sa = mgmt->sa;
#endif /* CONFIG_IEEE80211BE */
auth_alg = le_to_host16(mgmt->u.auth.auth_alg);
auth_transaction = le_to_host16(mgmt->u.auth.auth_transaction);
status_code = le_to_host16(mgmt->u.auth.status_code);
@ -2780,7 +2825,7 @@ static void handle_auth(struct hostapd_data *hapd,
wpa_printf(MSG_DEBUG, "authentication: STA=" MACSTR " auth_alg=%d "
"auth_transaction=%d status_code=%d wep=%d%s "
"seq_ctrl=0x%x%s%s",
MAC2STR(mgmt->sa), auth_alg, auth_transaction,
MAC2STR(sa), auth_alg, auth_transaction,
status_code, !!(fc & WLAN_FC_ISWEP),
challenge ? " challenge" : "",
seq_ctrl, (fc & WLAN_FC_RETRY) ? " retry" : "",
@ -2846,7 +2891,17 @@ static void handle_auth(struct hostapd_data *hapd,
if (os_memcmp(mgmt->sa, hapd->own_addr, ETH_ALEN) == 0) {
wpa_printf(MSG_INFO, "Station " MACSTR " not allowed to authenticate",
MAC2STR(mgmt->sa));
MAC2STR(sa));
resp = WLAN_STATUS_UNSPECIFIED_FAILURE;
goto fail;
}
if (mld_sta &&
(os_memcmp(sa, hapd->own_addr, ETH_ALEN) == 0 ||
os_memcmp(sa, hapd->mld_addr, ETH_ALEN) == 0)) {
wpa_printf(MSG_INFO,
"Station " MACSTR " not allowed to authenticate",
MAC2STR(sa));
resp = WLAN_STATUS_UNSPECIFIED_FAILURE;
goto fail;
}
@ -2854,7 +2909,7 @@ static void handle_auth(struct hostapd_data *hapd,
if (hapd->conf->no_auth_if_seen_on) {
struct hostapd_data *other;
other = sta_track_seen_on(hapd->iface, mgmt->sa,
other = sta_track_seen_on(hapd->iface, sa,
hapd->conf->no_auth_if_seen_on);
if (other) {
u8 *pos;
@ -2863,7 +2918,7 @@ static void handle_auth(struct hostapd_data *hapd,
wpa_printf(MSG_DEBUG, "%s: Reject authentication from "
MACSTR " since STA has been seen on %s",
hapd->conf->iface, MAC2STR(mgmt->sa),
hapd->conf->iface, MAC2STR(sa),
hapd->conf->no_auth_if_seen_on);
resp = WLAN_STATUS_REJECTED_WITH_SUGGESTED_BSS_TRANSITION;
@ -2906,12 +2961,12 @@ static void handle_auth(struct hostapd_data *hapd,
}
}
res = ieee802_11_allowed_address(hapd, mgmt->sa, (const u8 *) mgmt, len,
res = ieee802_11_allowed_address(hapd, sa, (const u8 *) mgmt, len,
&rad_info);
if (res == HOSTAPD_ACL_REJECT) {
wpa_msg(hapd->msg_ctx, MSG_DEBUG,
"Ignore Authentication frame from " MACSTR
" due to ACL reject", MAC2STR(mgmt->sa));
" due to ACL reject", MAC2STR(sa));
resp = WLAN_STATUS_UNSPECIFIED_FAILURE;
goto fail;
}
@ -2921,7 +2976,7 @@ static void handle_auth(struct hostapd_data *hapd,
#ifdef CONFIG_SAE
if (auth_alg == WLAN_AUTH_SAE && !from_queue &&
(auth_transaction == 1 ||
(auth_transaction == 2 && auth_sae_queued_addr(hapd, mgmt->sa)))) {
(auth_transaction == 2 && auth_sae_queued_addr(hapd, sa)))) {
/* Handle SAE Authentication commit message through a queue to
* provide more control for postponing the needed heavy
* processing under a possible DoS attack scenario. In addition,
@ -2934,7 +2989,7 @@ static void handle_auth(struct hostapd_data *hapd,
}
#endif /* CONFIG_SAE */
sta = ap_get_sta(hapd, mgmt->sa);
sta = ap_get_sta(hapd, sa);
if (sta) {
sta->flags &= ~WLAN_STA_PENDING_FILS_ERP;
sta->ft_over_ds = 0;
@ -2954,7 +3009,7 @@ static void handle_auth(struct hostapd_data *hapd,
sta->plink_state == PLINK_BLOCKED) {
wpa_printf(MSG_DEBUG, "Mesh peer " MACSTR
" is blocked - drop Authentication frame",
MAC2STR(mgmt->sa));
MAC2STR(sa));
return;
}
#endif /* CONFIG_MESH */
@ -2974,7 +3029,7 @@ static void handle_auth(struct hostapd_data *hapd,
*/
wpa_printf(MSG_DEBUG, "Mesh peer " MACSTR
" not yet known - drop Authentication frame",
MAC2STR(mgmt->sa));
MAC2STR(sa));
/*
* Save a copy of the frame so that it can be processed
* if a new peer entry is added shortly after this.
@ -2986,13 +3041,38 @@ static void handle_auth(struct hostapd_data *hapd,
}
#endif /* CONFIG_MESH */
sta = ap_sta_add(hapd, mgmt->sa);
sta = ap_sta_add(hapd, sa);
if (!sta) {
wpa_printf(MSG_DEBUG, "ap_sta_add() failed");
resp = WLAN_STATUS_AP_UNABLE_TO_HANDLE_NEW_STA;
goto fail;
}
}
#ifdef CONFIG_IEEE80211BE
if (auth_transaction == 1) {
os_memset(&sta->mld_info, 0, sizeof(sta->mld_info));
if (mld_sta) {
u8 link_id = hapd->mld_link_id;
sta->mld_info.mld_sta = true;
sta->mld_assoc_link_id = link_id;
/*
* Set the MLD address as the station address and the
* station addresses.
*/
os_memcpy(sta->mld_info.common_info.mld_addr, sa,
ETH_ALEN);
os_memcpy(sta->mld_info.links[link_id].peer_addr,
mgmt->sa, ETH_ALEN);
os_memcpy(sta->mld_info.links[link_id].local_addr,
hapd->own_addr, ETH_ALEN);
}
}
#endif /* CONFIG_IEEE80211BE */
sta->last_seq_ctrl = seq_ctrl;
sta->last_subtype = WLAN_FC_STYPE_AUTH;
#ifdef CONFIG_MBO
@ -3130,7 +3210,22 @@ static void handle_auth(struct hostapd_data *hapd,
}
fail:
reply_res = send_auth_reply(hapd, sta, mgmt->sa, mgmt->bssid, auth_alg,
dst = mgmt->sa;
bssid = mgmt->bssid;
#ifdef CONFIG_IEEE80211BE
/*
* Once a non-AP MLD is added to the driver, the addressing should use
* the MLD MAC address. It is the responsibility of the driver to
* handle the translations.
*/
if (hapd->conf->mld_ap && sta && sta->mld_info.mld_sta) {
dst = sta->addr;
bssid = hapd->mld_addr;
}
#endif /* CONFIG_IEEE80211BE */
reply_res = send_auth_reply(hapd, sta, dst, bssid, auth_alg,
auth_alg == WLAN_AUTH_SAE ?
auth_transaction : auth_transaction + 1,
resp, resp_ies, resp_ies_len,

View file

@ -86,6 +86,10 @@ void hostapd_get_eht_capab(struct hostapd_data *hapd,
size_t len);
u8 * hostapd_eid_eht_basic_ml(struct hostapd_data *hapd, u8 *eid,
struct sta_info *info, bool include_mld_id);
struct wpabuf * hostapd_ml_auth_resp(struct hostapd_data *hapd);
const u8 * hostapd_process_ml_auth(struct hostapd_data *hapd,
const struct ieee80211_mgmt *mgmt,
size_t len);
int hostapd_get_aid(struct hostapd_data *hapd, struct sta_info *sta);
u16 copy_sta_ht_capab(struct hostapd_data *hapd, struct sta_info *sta,
const u8 *ht_capab);

View file

@ -8,6 +8,7 @@
#include "utils/includes.h"
#include "utils/common.h"
#include "crypto/dh_groups.h"
#include "hostapd.h"
#include "sta_info.h"
#include "ieee802_11.h"
@ -621,3 +622,208 @@ out:
wpabuf_free(buf);
return pos;
}
struct wpabuf * hostapd_ml_auth_resp(struct hostapd_data *hapd)
{
struct wpabuf *buf = wpabuf_alloc(12);
if (!buf)
return NULL;
wpabuf_put_u8(buf, WLAN_EID_EXTENSION);
wpabuf_put_u8(buf, 10);
wpabuf_put_u8(buf, WLAN_EID_EXT_MULTI_LINK);
wpabuf_put_le16(buf, MULTI_LINK_CONTROL_TYPE_BASIC);
wpabuf_put_u8(buf, ETH_ALEN + 1);
wpabuf_put_data(buf, hapd->mld_addr, ETH_ALEN);
return buf;
}
static const u8 *
sae_commit_skip_fixed_fields(const struct ieee80211_mgmt *mgmt, size_t len,
const u8 *pos, u16 status_code)
{
u16 group;
size_t prime_len;
struct crypto_ec *ec;
if (status_code != WLAN_STATUS_SAE_HASH_TO_ELEMENT)
return pos;
/* SAE H2E commit message (group, scalar, FFE) */
if (len < 2) {
wpa_printf(MSG_DEBUG,
"EHT: SAE Group is not present");
return NULL;
}
group = WPA_GET_LE16(pos);
pos += 2;
/* TODO: How to parse when the group is unknown? */
ec = crypto_ec_init(group);
if (!ec) {
const struct dh_group *dh = dh_groups_get(group);
if (!dh) {
wpa_printf(MSG_DEBUG, "EHT: Unknown SAE group %u",
group);
return NULL;
}
prime_len = dh->prime_len;
} else {
prime_len = crypto_ec_prime_len(ec);
}
wpa_printf(MSG_DEBUG, "EHT: SAE scalar length is %zu", prime_len);
/* scalar */
pos += prime_len;
if (ec) {
pos += prime_len * 2;
crypto_ec_deinit(ec);
} else {
pos += prime_len;
}
if (pos - mgmt->u.auth.variable > (int) len) {
wpa_printf(MSG_DEBUG,
"EHT: Too short SAE commit Authentication frame");
return NULL;
}
wpa_hexdump(MSG_DEBUG, "EHT: SAE: Authentication frame elements",
pos, (int) len - (pos - mgmt->u.auth.variable));
return pos;
}
static const u8 *
sae_confirm_skip_fixed_fields(struct hostapd_data *hapd,
const struct ieee80211_mgmt *mgmt, size_t len,
const u8 *pos, u16 status_code)
{
struct sta_info *sta;
if (status_code == WLAN_STATUS_REJECTED_WITH_SUGGESTED_BSS_TRANSITION)
return pos;
/* send confirm integer */
pos += 2;
/*
* At this stage we should already have an MLD station and actually SA
* will be replaced with the MLD MAC address by the driver.
*/
sta = ap_get_sta(hapd, mgmt->sa);
if (!sta) {
wpa_printf(MSG_DEBUG, "SAE: No MLD STA for SAE confirm");
return NULL;
}
if (!sta->sae || sta->sae->state < SAE_COMMITTED || !sta->sae->tmp) {
if (sta->sae)
wpa_printf(MSG_DEBUG, "SAE: Invalid state=%u",
sta->sae->state);
else
wpa_printf(MSG_DEBUG, "SAE: No SAE context");
return NULL;
}
wpa_printf(MSG_DEBUG, "SAE: confirm: kck_len=%zu",
sta->sae->tmp->kck_len);
pos += sta->sae->tmp->kck_len;
if (pos - mgmt->u.auth.variable > (int) len) {
wpa_printf(MSG_DEBUG,
"EHT: Too short SAE confirm Authentication frame");
return NULL;
}
return pos;
}
static const u8 * auth_skip_fixed_fields(struct hostapd_data *hapd,
const struct ieee80211_mgmt *mgmt,
size_t len)
{
u16 auth_alg = le_to_host16(mgmt->u.auth.auth_alg);
u16 auth_transaction = le_to_host16(mgmt->u.auth.auth_transaction);
u16 status_code = le_to_host16(mgmt->u.auth.status_code);
const u8 *pos = mgmt->u.auth.variable;
/* Skip fixed fields as based on IEE P802.11-REVme/D3.0, Table 9-69
* (Presence of fields and elements in Authentications frames) */
switch (auth_alg) {
case WLAN_AUTH_OPEN:
return pos;
case WLAN_AUTH_SAE:
if (auth_transaction == 1) {
if (status_code == WLAN_STATUS_SUCCESS) {
wpa_printf(MSG_DEBUG,
"EHT: SAE H2E is mandatory for MLD");
goto out;
}
return sae_commit_skip_fixed_fields(mgmt, len, pos,
status_code);
} else if (auth_transaction == 2) {
return sae_confirm_skip_fixed_fields(hapd, mgmt, len,
pos, status_code);
}
return pos;
/* TODO: Support additional algorithms that can be used for MLO */
case WLAN_AUTH_FT:
case WLAN_AUTH_FILS_SK:
case WLAN_AUTH_FILS_SK_PFS:
case WLAN_AUTH_FILS_PK:
case WLAN_AUTH_PASN:
default:
break;
}
out:
wpa_printf(MSG_DEBUG,
"TODO: Authentication algorithm %u not supported with MLD",
auth_alg);
return NULL;
}
const u8 * hostapd_process_ml_auth(struct hostapd_data *hapd,
const struct ieee80211_mgmt *mgmt,
size_t len)
{
struct ieee802_11_elems elems;
const u8 *pos;
if (!hapd->conf->mld_ap)
return NULL;
len -= offsetof(struct ieee80211_mgmt, u.auth.variable);
pos = auth_skip_fixed_fields(hapd, mgmt, len);
if (!pos)
return NULL;
if (ieee802_11_parse_elems(pos,
(int)len - (pos - mgmt->u.auth.variable),
&elems, 0) == ParseFailed) {
wpa_printf(MSG_DEBUG,
"MLD: Failed parsing Authentication frame");
}
if (!elems.basic_mle || !elems.basic_mle_len)
return NULL;
return get_basic_mle_mld_addr(elems.basic_mle, elems.basic_mle_len);
}