blob: 40d4a3381786e133797ab67b50633764dd82a44b [file] [log] [blame]
/*
* hostapd / IEEE 802.11 Management
* Copyright (c) 2002-2017, Jouni Malinen <j@w1.fi>
*
* This software may be distributed under the terms of the BSD license.
* See README for more details.
*/
#include "utils/includes.h"
#ifndef CONFIG_NATIVE_WINDOWS
#include "utils/common.h"
#include "utils/eloop.h"
#include "crypto/crypto.h"
#include "crypto/sha256.h"
#include "crypto/sha384.h"
#include "crypto/sha512.h"
#include "crypto/random.h"
#include "common/ieee802_11_defs.h"
#include "common/ieee802_11_common.h"
#include "common/wpa_ctrl.h"
#include "common/sae.h"
#include "common/dpp.h"
#include "common/ocv.h"
#include "common/wpa_common.h"
#include "common/wpa_ctrl.h"
#include "common/ptksa_cache.h"
#include "radius/radius.h"
#include "radius/radius_client.h"
#include "p2p/p2p.h"
#include "wps/wps.h"
#include "fst/fst.h"
#include "hostapd.h"
#include "beacon.h"
#include "ieee802_11_auth.h"
#include "sta_info.h"
#include "ieee802_1x.h"
#include "wpa_auth.h"
#include "pmksa_cache_auth.h"
#include "wmm.h"
#include "ap_list.h"
#include "accounting.h"
#include "ap_config.h"
#include "ap_mlme.h"
#include "p2p_hostapd.h"
#include "ap_drv_ops.h"
#include "wnm_ap.h"
#include "hw_features.h"
#include "ieee802_11.h"
#include "dfs.h"
#include "mbo_ap.h"
#include "rrm.h"
#include "taxonomy.h"
#include "fils_hlp.h"
#include "dpp_hostapd.h"
#include "gas_query_ap.h"
#ifdef CONFIG_FILS
static struct wpabuf *
prepare_auth_resp_fils(struct hostapd_data *hapd,
struct sta_info *sta, u16 *resp,
struct rsn_pmksa_cache_entry *pmksa,
struct wpabuf *erp_resp,
const u8 *msk, size_t msk_len,
int *is_pub);
#endif /* CONFIG_FILS */
#ifdef CONFIG_PASN
static int handle_auth_pasn_resp(struct hostapd_data *hapd,
struct sta_info *sta,
struct rsn_pmksa_cache_entry *pmksa,
u16 status);
#ifdef CONFIG_FILS
static void pasn_fils_auth_resp(struct hostapd_data *hapd,
struct sta_info *sta, u16 status,
struct wpabuf *erp_resp,
const u8 *msk, size_t msk_len);
#endif /* CONFIG_FILS */
#endif /* CONFIG_PASN */
static void handle_auth(struct hostapd_data *hapd,
const struct ieee80211_mgmt *mgmt, size_t len,
int rssi, int from_queue);
u8 * hostapd_eid_multi_ap(struct hostapd_data *hapd, u8 *eid)
{
u8 multi_ap_val = 0;
if (!hapd->conf->multi_ap)
return eid;
if (hapd->conf->multi_ap & BACKHAUL_BSS)
multi_ap_val |= MULTI_AP_BACKHAUL_BSS;
if (hapd->conf->multi_ap & FRONTHAUL_BSS)
multi_ap_val |= MULTI_AP_FRONTHAUL_BSS;
return eid + add_multi_ap_ie(eid, 9, multi_ap_val);
}
u8 * hostapd_eid_supp_rates(struct hostapd_data *hapd, u8 *eid)
{
u8 *pos = eid;
int i, num, count;
int h2e_required;
if (hapd->iface->current_rates == NULL)
return eid;
*pos++ = WLAN_EID_SUPP_RATES;
num = hapd->iface->num_rates;
if (hapd->iconf->ieee80211n && hapd->iconf->require_ht)
num++;
if (hapd->iconf->ieee80211ac && hapd->iconf->require_vht)
num++;
h2e_required = (hapd->conf->sae_pwe == 1 ||
hostapd_sae_pw_id_in_use(hapd->conf) == 2) &&
hapd->conf->sae_pwe != 3 &&
wpa_key_mgmt_sae(hapd->conf->wpa_key_mgmt);
if (h2e_required)
num++;
if (num > 8) {
/* rest of the rates are encoded in Extended supported
* rates element */
num = 8;
}
*pos++ = num;
for (i = 0, count = 0; i < hapd->iface->num_rates && count < num;
i++) {
count++;
*pos = hapd->iface->current_rates[i].rate / 5;
if (hapd->iface->current_rates[i].flags & HOSTAPD_RATE_BASIC)
*pos |= 0x80;
pos++;
}
if (hapd->iconf->ieee80211n && hapd->iconf->require_ht && count < 8) {
count++;
*pos++ = 0x80 | BSS_MEMBERSHIP_SELECTOR_HT_PHY;
}
if (hapd->iconf->ieee80211ac && hapd->iconf->require_vht && count < 8) {
count++;
*pos++ = 0x80 | BSS_MEMBERSHIP_SELECTOR_VHT_PHY;
}
if (h2e_required && count < 8) {
count++;
*pos++ = 0x80 | BSS_MEMBERSHIP_SELECTOR_SAE_H2E_ONLY;
}
return pos;
}
u8 * hostapd_eid_ext_supp_rates(struct hostapd_data *hapd, u8 *eid)
{
u8 *pos = eid;
int i, num, count;
int h2e_required;
if (hapd->iface->current_rates == NULL)
return eid;
num = hapd->iface->num_rates;
if (hapd->iconf->ieee80211n && hapd->iconf->require_ht)
num++;
if (hapd->iconf->ieee80211ac && hapd->iconf->require_vht)
num++;
h2e_required = (hapd->conf->sae_pwe == 1 ||
hostapd_sae_pw_id_in_use(hapd->conf) == 2) &&
hapd->conf->sae_pwe != 3 &&
wpa_key_mgmt_sae(hapd->conf->wpa_key_mgmt);
if (h2e_required)
num++;
if (num <= 8)
return eid;
num -= 8;
*pos++ = WLAN_EID_EXT_SUPP_RATES;
*pos++ = num;
for (i = 0, count = 0; i < hapd->iface->num_rates && count < num + 8;
i++) {
count++;
if (count <= 8)
continue; /* already in SuppRates IE */
*pos = hapd->iface->current_rates[i].rate / 5;
if (hapd->iface->current_rates[i].flags & HOSTAPD_RATE_BASIC)
*pos |= 0x80;
pos++;
}
if (hapd->iconf->ieee80211n && hapd->iconf->require_ht) {
count++;
if (count > 8)
*pos++ = 0x80 | BSS_MEMBERSHIP_SELECTOR_HT_PHY;
}
if (hapd->iconf->ieee80211ac && hapd->iconf->require_vht) {
count++;
if (count > 8)
*pos++ = 0x80 | BSS_MEMBERSHIP_SELECTOR_VHT_PHY;
}
if (h2e_required) {
count++;
if (count > 8)
*pos++ = 0x80 | BSS_MEMBERSHIP_SELECTOR_SAE_H2E_ONLY;
}
return pos;
}
u8 * hostapd_eid_rm_enabled_capab(struct hostapd_data *hapd, u8 *eid,
size_t len)
{
size_t i;
for (i = 0; i < RRM_CAPABILITIES_IE_LEN; i++) {
if (hapd->conf->radio_measurements[i])
break;
}
if (i == RRM_CAPABILITIES_IE_LEN || len < 2 + RRM_CAPABILITIES_IE_LEN)
return eid;
*eid++ = WLAN_EID_RRM_ENABLED_CAPABILITIES;
*eid++ = RRM_CAPABILITIES_IE_LEN;
os_memcpy(eid, hapd->conf->radio_measurements, RRM_CAPABILITIES_IE_LEN);
return eid + RRM_CAPABILITIES_IE_LEN;
}
u16 hostapd_own_capab_info(struct hostapd_data *hapd)
{
int capab = WLAN_CAPABILITY_ESS;
int privacy = 0;
int dfs;
int i;
/* Check if any of configured channels require DFS */
dfs = hostapd_is_dfs_required(hapd->iface);
if (dfs < 0) {
wpa_printf(MSG_WARNING, "Failed to check if DFS is required; ret=%d",
dfs);
dfs = 0;
}
if (hapd->iface->num_sta_no_short_preamble == 0 &&
hapd->iconf->preamble == SHORT_PREAMBLE)
capab |= WLAN_CAPABILITY_SHORT_PREAMBLE;
#ifdef CONFIG_WEP
privacy = hapd->conf->ssid.wep.keys_set;
if (hapd->conf->ieee802_1x &&
(hapd->conf->default_wep_key_len ||
hapd->conf->individual_wep_key_len))
privacy = 1;
#endif /* CONFIG_WEP */
if (hapd->conf->wpa)
privacy = 1;
#ifdef CONFIG_HS20
if (hapd->conf->osen)
privacy = 1;
#endif /* CONFIG_HS20 */
if (privacy)
capab |= WLAN_CAPABILITY_PRIVACY;
if (hapd->iface->current_mode &&
hapd->iface->current_mode->mode == HOSTAPD_MODE_IEEE80211G &&
hapd->iface->num_sta_no_short_slot_time == 0)
capab |= WLAN_CAPABILITY_SHORT_SLOT_TIME;
/*
* Currently, Spectrum Management capability bit is set when directly
* requested in configuration by spectrum_mgmt_required or when AP is
* running on DFS channel.
* TODO: Also consider driver support for TPC to set Spectrum Mgmt bit
*/
if (hapd->iface->current_mode &&
hapd->iface->current_mode->mode == HOSTAPD_MODE_IEEE80211A &&
(hapd->iconf->spectrum_mgmt_required || dfs))
capab |= WLAN_CAPABILITY_SPECTRUM_MGMT;
for (i = 0; i < RRM_CAPABILITIES_IE_LEN; i++) {
if (hapd->conf->radio_measurements[i]) {
capab |= IEEE80211_CAP_RRM;
break;
}
}
return capab;
}
#ifdef CONFIG_WEP
#ifndef CONFIG_NO_RC4
static u16 auth_shared_key(struct hostapd_data *hapd, struct sta_info *sta,
u16 auth_transaction, const u8 *challenge,
int iswep)
{
hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211,
HOSTAPD_LEVEL_DEBUG,
"authentication (shared key, transaction %d)",
auth_transaction);
if (auth_transaction == 1) {
if (!sta->challenge) {
/* Generate a pseudo-random challenge */
u8 key[8];
sta->challenge = os_zalloc(WLAN_AUTH_CHALLENGE_LEN);
if (sta->challenge == NULL)
return WLAN_STATUS_UNSPECIFIED_FAILURE;
if (os_get_random(key, sizeof(key)) < 0) {
os_free(sta->challenge);
sta->challenge = NULL;
return WLAN_STATUS_UNSPECIFIED_FAILURE;
}
rc4_skip(key, sizeof(key), 0,
sta->challenge, WLAN_AUTH_CHALLENGE_LEN);
}
return 0;
}
if (auth_transaction != 3)
return WLAN_STATUS_UNSPECIFIED_FAILURE;
/* Transaction 3 */
if (!iswep || !sta->challenge || !challenge ||
os_memcmp_const(sta->challenge, challenge,
WLAN_AUTH_CHALLENGE_LEN)) {
hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211,
HOSTAPD_LEVEL_INFO,
"shared key authentication - invalid "
"challenge-response");
return WLAN_STATUS_CHALLENGE_FAIL;
}
hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211,
HOSTAPD_LEVEL_DEBUG,
"authentication OK (shared key)");
sta->flags |= WLAN_STA_AUTH;
wpa_auth_sm_event(sta->wpa_sm, WPA_AUTH);
os_free(sta->challenge);
sta->challenge = NULL;
return 0;
}
#endif /* CONFIG_NO_RC4 */
#endif /* CONFIG_WEP */
static int send_auth_reply(struct hostapd_data *hapd, struct sta_info *sta,
const u8 *dst, const u8 *bssid,
u16 auth_alg, u16 auth_transaction, u16 resp,
const u8 *ies, size_t ies_len, const char *dbg)
{
struct ieee80211_mgmt *reply;
u8 *buf;
size_t rlen;
int reply_res = WLAN_STATUS_UNSPECIFIED_FAILURE;
rlen = IEEE80211_HDRLEN + sizeof(reply->u.auth) + ies_len;
buf = os_zalloc(rlen);
if (buf == NULL)
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->bssid, bssid, ETH_ALEN);
reply->u.auth.auth_alg = host_to_le16(auth_alg);
reply->u.auth.auth_transaction = host_to_le16(auth_transaction);
reply->u.auth.status_code = host_to_le16(resp);
if (ies && ies_len)
os_memcpy(reply->u.auth.variable, ies, ies_len);
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,
resp, (unsigned long) ies_len, dbg);
#ifdef CONFIG_TESTING_OPTIONS
#ifdef CONFIG_SAE
if (hapd->conf->sae_confirm_immediate == 2 &&
auth_alg == WLAN_AUTH_SAE) {
if (auth_transaction == 1 && sta &&
(resp == WLAN_STATUS_SUCCESS ||
resp == WLAN_STATUS_SAE_HASH_TO_ELEMENT ||
resp == WLAN_STATUS_SAE_PK)) {
wpa_printf(MSG_DEBUG,
"TESTING: Postpone SAE Commit transmission until Confirm is ready");
os_free(sta->sae_postponed_commit);
sta->sae_postponed_commit = buf;
sta->sae_postponed_commit_len = rlen;
return WLAN_STATUS_SUCCESS;
}
if (auth_transaction == 2 && sta && sta->sae_postponed_commit) {
wpa_printf(MSG_DEBUG,
"TESTING: Send postponed SAE Commit first, immediately followed by SAE Confirm");
if (hostapd_drv_send_mlme(hapd,
sta->sae_postponed_commit,
sta->sae_postponed_commit_len,
0, NULL, 0, 0) < 0)
wpa_printf(MSG_INFO, "send_auth_reply: send failed");
os_free(sta->sae_postponed_commit);
sta->sae_postponed_commit = NULL;
sta->sae_postponed_commit_len = 0;
}
}
#endif /* CONFIG_SAE */
#endif /* CONFIG_TESTING_OPTIONS */
if (hostapd_drv_send_mlme(hapd, reply, rlen, 0, NULL, 0, 0) < 0)
wpa_printf(MSG_INFO, "send_auth_reply: send failed");
else
reply_res = WLAN_STATUS_SUCCESS;
os_free(buf);
return reply_res;
}
#ifdef CONFIG_IEEE80211R_AP
static void handle_auth_ft_finish(void *ctx, const u8 *dst, const u8 *bssid,
u16 auth_transaction, u16 status,
const u8 *ies, size_t ies_len)
{
struct hostapd_data *hapd = ctx;
struct sta_info *sta;
int reply_res;
reply_res = send_auth_reply(hapd, NULL, dst, bssid, WLAN_AUTH_FT,
auth_transaction, status, ies, ies_len,
"auth-ft-finish");
sta = ap_get_sta(hapd, dst);
if (sta == NULL)
return;
if (sta->added_unassoc && (reply_res != WLAN_STATUS_SUCCESS ||
status != WLAN_STATUS_SUCCESS)) {
hostapd_drv_sta_remove(hapd, sta->addr);
sta->added_unassoc = 0;
return;
}
if (status != WLAN_STATUS_SUCCESS)
return;
hostapd_logger(hapd, dst, HOSTAPD_MODULE_IEEE80211,
HOSTAPD_LEVEL_DEBUG, "authentication OK (FT)");
sta->flags |= WLAN_STA_AUTH;
mlme_authenticate_indication(hapd, sta);
}
#endif /* CONFIG_IEEE80211R_AP */
#ifdef CONFIG_SAE
static void sae_set_state(struct sta_info *sta, enum sae_state state,
const char *reason)
{
wpa_printf(MSG_DEBUG, "SAE: State %s -> %s for peer " MACSTR " (%s)",
sae_state_txt(sta->sae->state), sae_state_txt(state),
MAC2STR(sta->addr), reason);
sta->sae->state = state;
}
static const char * sae_get_password(struct hostapd_data *hapd,
struct sta_info *sta,
const char *rx_id,
struct sae_password_entry **pw_entry,
struct sae_pt **s_pt,
const struct sae_pk **s_pk)
{
const char *password = NULL;
struct sae_password_entry *pw;
struct sae_pt *pt = NULL;
const struct sae_pk *pk = NULL;
for (pw = hapd->conf->sae_passwords; pw; pw = pw->next) {
if (!is_broadcast_ether_addr(pw->peer_addr) &&
os_memcmp(pw->peer_addr, sta->addr, ETH_ALEN) != 0)
continue;
if ((rx_id && !pw->identifier) || (!rx_id && pw->identifier))
continue;
if (rx_id && pw->identifier &&
os_strcmp(rx_id, pw->identifier) != 0)
continue;
password = pw->password;
pt = pw->pt;
if (!(hapd->conf->mesh & MESH_ENABLED))
pk = pw->pk;
break;
}
if (!password) {
password = hapd->conf->ssid.wpa_passphrase;
pt = hapd->conf->ssid.pt;
}
if (pw_entry)
*pw_entry = pw;
if (s_pt)
*s_pt = pt;
if (s_pk)
*s_pk = pk;
return password;
}
static struct wpabuf * auth_build_sae_commit(struct hostapd_data *hapd,
struct sta_info *sta, int update,
int status_code)
{
struct wpabuf *buf;
const char *password = NULL;
struct sae_password_entry *pw;
const char *rx_id = NULL;
int use_pt = 0;
struct sae_pt *pt = NULL;
const struct sae_pk *pk = NULL;
if (sta->sae->tmp) {
rx_id = sta->sae->tmp->pw_id;
use_pt = sta->sae->h2e;
#ifdef CONFIG_SAE_PK
os_memcpy(sta->sae->tmp->own_addr, hapd->own_addr, ETH_ALEN);
os_memcpy(sta->sae->tmp->peer_addr, sta->addr, ETH_ALEN);
#endif /* CONFIG_SAE_PK */
}
if (rx_id && hapd->conf->sae_pwe != 3)
use_pt = 1;
else if (status_code == WLAN_STATUS_SUCCESS)
use_pt = 0;
else if (status_code == WLAN_STATUS_SAE_HASH_TO_ELEMENT ||
status_code == WLAN_STATUS_SAE_PK)
use_pt = 1;
password = sae_get_password(hapd, sta, rx_id, &pw, &pt, &pk);
if (!password || (use_pt && !pt)) {
wpa_printf(MSG_DEBUG, "SAE: No password available");
return NULL;
}
if (update && use_pt &&
sae_prepare_commit_pt(sta->sae, pt, hapd->own_addr, sta->addr,
NULL, pk) < 0)
return NULL;
if (update && !use_pt &&
sae_prepare_commit(hapd->own_addr, sta->addr,
(u8 *) password, os_strlen(password), rx_id,
sta->sae) < 0) {
wpa_printf(MSG_DEBUG, "SAE: Could not pick PWE");
return NULL;
}
if (pw && pw->vlan_id) {
if (!sta->sae->tmp) {
wpa_printf(MSG_INFO,
"SAE: No temporary data allocated - cannot store VLAN ID");
return NULL;
}
sta->sae->tmp->vlan_id = pw->vlan_id;
}
buf = wpabuf_alloc(SAE_COMMIT_MAX_LEN +
(rx_id ? 3 + os_strlen(rx_id) : 0));
if (buf &&
sae_write_commit(sta->sae, buf, sta->sae->tmp ?
sta->sae->tmp->anti_clogging_token : NULL,
rx_id) < 0) {
wpabuf_free(buf);
buf = NULL;
}
return buf;
}
static struct wpabuf * auth_build_sae_confirm(struct hostapd_data *hapd,
struct sta_info *sta)
{
struct wpabuf *buf;
buf = wpabuf_alloc(SAE_CONFIRM_MAX_LEN);
if (buf == NULL)
return NULL;
#ifdef CONFIG_SAE_PK
#ifdef CONFIG_TESTING_OPTIONS
if (sta->sae->tmp)
sta->sae->tmp->omit_pk_elem = hapd->conf->sae_pk_omit;
#endif /* CONFIG_TESTING_OPTIONS */
#endif /* CONFIG_SAE_PK */
if (sae_write_confirm(sta->sae, buf) < 0) {
wpabuf_free(buf);
return NULL;
}
return buf;
}
static int auth_sae_send_commit(struct hostapd_data *hapd,
struct sta_info *sta,
const u8 *bssid, int update, int status_code)
{
struct wpabuf *data;
int reply_res;
u16 status;
data = auth_build_sae_commit(hapd, sta, update, status_code);
if (!data && sta->sae->tmp && sta->sae->tmp->pw_id)
return WLAN_STATUS_UNKNOWN_PASSWORD_IDENTIFIER;
if (data == NULL)
return WLAN_STATUS_UNSPECIFIED_FAILURE;
if (sta->sae->tmp && sta->sae->pk)
status = WLAN_STATUS_SAE_PK;
else if (sta->sae->tmp && sta->sae->h2e)
status = WLAN_STATUS_SAE_HASH_TO_ELEMENT;
else
status = WLAN_STATUS_SUCCESS;
#ifdef CONFIG_TESTING_OPTIONS
if (hapd->conf->sae_commit_status >= 0 &&
hapd->conf->sae_commit_status != status) {
wpa_printf(MSG_INFO,
"TESTING: Override SAE commit status code %u --> %d",
status, hapd->conf->sae_commit_status);
status = hapd->conf->sae_commit_status;
}
#endif /* CONFIG_TESTING_OPTIONS */
reply_res = send_auth_reply(hapd, sta, sta->addr, bssid,
WLAN_AUTH_SAE, 1,
status, wpabuf_head(data),
wpabuf_len(data), "sae-send-commit");
wpabuf_free(data);
return reply_res;
}
static int auth_sae_send_confirm(struct hostapd_data *hapd,
struct sta_info *sta,
const u8 *bssid)
{
struct wpabuf *data;
int reply_res;
data = auth_build_sae_confirm(hapd, sta);
if (data == NULL)
return WLAN_STATUS_UNSPECIFIED_FAILURE;
reply_res = send_auth_reply(hapd, sta, sta->addr, bssid,
WLAN_AUTH_SAE, 2,
WLAN_STATUS_SUCCESS, wpabuf_head(data),
wpabuf_len(data), "sae-send-confirm");
wpabuf_free(data);
return reply_res;
}
static int use_sae_anti_clogging(struct hostapd_data *hapd)
{
struct sta_info *sta;
unsigned int open = 0;
if (hapd->conf->sae_anti_clogging_threshold == 0)
return 1;
for (sta = hapd->sta_list; sta; sta = sta->next) {
if (!sta->sae)
continue;
if (sta->sae->state != SAE_COMMITTED &&
sta->sae->state != SAE_CONFIRMED)
continue;
open++;
if (open >= hapd->conf->sae_anti_clogging_threshold)
return 1;
}
/* In addition to already existing open SAE sessions, check whether
* there are enough pending commit messages in the processing queue to
* potentially result in too many open sessions. */
if (open + dl_list_len(&hapd->sae_commit_queue) >=
hapd->conf->sae_anti_clogging_threshold)
return 1;
return 0;
}
static int sae_token_hash(struct hostapd_data *hapd, const u8 *addr, u8 *idx)
{
u8 hash[SHA256_MAC_LEN];
if (hmac_sha256(hapd->sae_token_key, sizeof(hapd->sae_token_key),
addr, ETH_ALEN, hash) < 0)
return -1;
*idx = hash[0];
return 0;
}
static int check_sae_token(struct hostapd_data *hapd, const u8 *addr,
const u8 *token, size_t token_len)
{
u8 mac[SHA256_MAC_LEN];
const u8 *addrs[2];
size_t len[2];
u16 token_idx;
u8 idx;
if (token_len != SHA256_MAC_LEN || sae_token_hash(hapd, addr, &idx) < 0)
return -1;
token_idx = hapd->sae_pending_token_idx[idx];
if (token_idx == 0 || token_idx != WPA_GET_BE16(token)) {
wpa_printf(MSG_DEBUG, "SAE: Invalid anti-clogging token from "
MACSTR " - token_idx 0x%04x, expected 0x%04x",
MAC2STR(addr), WPA_GET_BE16(token), token_idx);
return -1;
}
addrs[0] = addr;
len[0] = ETH_ALEN;
addrs[1] = token;
len[1] = 2;
if (hmac_sha256_vector(hapd->sae_token_key, sizeof(hapd->sae_token_key),
2, addrs, len, mac) < 0 ||
os_memcmp_const(token + 2, &mac[2], SHA256_MAC_LEN - 2) != 0)
return -1;
hapd->sae_pending_token_idx[idx] = 0; /* invalidate used token */
return 0;
}
static struct wpabuf * auth_build_token_req(struct hostapd_data *hapd,
int group, const u8 *addr, int h2e)
{
struct wpabuf *buf;
u8 *token;
struct os_reltime now;
u8 idx[2];
const u8 *addrs[2];
size_t len[2];
u8 p_idx;
u16 token_idx;
os_get_reltime(&now);
if (!os_reltime_initialized(&hapd->last_sae_token_key_update) ||
os_reltime_expired(&now, &hapd->last_sae_token_key_update, 60) ||
hapd->sae_token_idx == 0xffff) {
if (random_get_bytes(hapd->sae_token_key,
sizeof(hapd->sae_token_key)) < 0)
return NULL;
wpa_hexdump(MSG_DEBUG, "SAE: Updated token key",
hapd->sae_token_key, sizeof(hapd->sae_token_key));
hapd->last_sae_token_key_update = now;
hapd->sae_token_idx = 0;
os_memset(hapd->sae_pending_token_idx, 0,
sizeof(hapd->sae_pending_token_idx));
}
buf = wpabuf_alloc(sizeof(le16) + 3 + SHA256_MAC_LEN);
if (buf == NULL)
return NULL;
wpabuf_put_le16(buf, group); /* Finite Cyclic Group */
if (h2e) {
/* Encapsulate Anti-clogging Token field in a container IE */
wpabuf_put_u8(buf, WLAN_EID_EXTENSION);
wpabuf_put_u8(buf, 1 + SHA256_MAC_LEN);
wpabuf_put_u8(buf, WLAN_EID_EXT_ANTI_CLOGGING_TOKEN);
}
if (sae_token_hash(hapd, addr, &p_idx) < 0) {
wpabuf_free(buf);
return NULL;
}
token_idx = hapd->sae_pending_token_idx[p_idx];
if (!token_idx) {
hapd->sae_token_idx++;
token_idx = hapd->sae_token_idx;
hapd->sae_pending_token_idx[p_idx] = token_idx;
}
WPA_PUT_BE16(idx, token_idx);
token = wpabuf_put(buf, SHA256_MAC_LEN);
addrs[0] = addr;
len[0] = ETH_ALEN;
addrs[1] = idx;
len[1] = sizeof(idx);
if (hmac_sha256_vector(hapd->sae_token_key, sizeof(hapd->sae_token_key),
2, addrs, len, token) < 0) {
wpabuf_free(buf);
return NULL;
}
WPA_PUT_BE16(token, token_idx);
return buf;
}
static int sae_check_big_sync(struct hostapd_data *hapd, struct sta_info *sta)
{
if (sta->sae->sync > hapd->conf->sae_sync) {
sae_set_state(sta, SAE_NOTHING, "Sync > dot11RSNASAESync");
sta->sae->sync = 0;
return -1;
}
return 0;
}
static void auth_sae_retransmit_timer(void *eloop_ctx, void *eloop_data)
{
struct hostapd_data *hapd = eloop_ctx;
struct sta_info *sta = eloop_data;
int ret;
if (sae_check_big_sync(hapd, sta))
return;
sta->sae->sync++;
wpa_printf(MSG_DEBUG, "SAE: Auth SAE retransmit timer for " MACSTR
" (sync=%d state=%s)",
MAC2STR(sta->addr), sta->sae->sync,
sae_state_txt(sta->sae->state));
switch (sta->sae->state) {
case SAE_COMMITTED:
ret = auth_sae_send_commit(hapd, sta, hapd->own_addr, 0, -1);
eloop_register_timeout(0,
hapd->dot11RSNASAERetransPeriod * 1000,
auth_sae_retransmit_timer, hapd, sta);
break;
case SAE_CONFIRMED:
ret = auth_sae_send_confirm(hapd, sta, hapd->own_addr);
eloop_register_timeout(0,
hapd->dot11RSNASAERetransPeriod * 1000,
auth_sae_retransmit_timer, hapd, sta);
break;
default:
ret = -1;
break;
}
if (ret != WLAN_STATUS_SUCCESS)
wpa_printf(MSG_INFO, "SAE: Failed to retransmit: ret=%d", ret);
}
void sae_clear_retransmit_timer(struct hostapd_data *hapd, struct sta_info *sta)
{
eloop_cancel_timeout(auth_sae_retransmit_timer, hapd, sta);
}
static void sae_set_retransmit_timer(struct hostapd_data *hapd,
struct sta_info *sta)
{
if (!(hapd->conf->mesh & MESH_ENABLED))
return;
eloop_cancel_timeout(auth_sae_retransmit_timer, hapd, sta);
eloop_register_timeout(0, hapd->dot11RSNASAERetransPeriod * 1000,
auth_sae_retransmit_timer, hapd, sta);
}
static void sae_sme_send_external_auth_status(struct hostapd_data *hapd,
struct sta_info *sta, u16 status)
{
struct external_auth params;
os_memset(&params, 0, sizeof(params));
params.status = status;
params.bssid = sta->addr;
if (status == WLAN_STATUS_SUCCESS && sta->sae &&
!hapd->conf->disable_pmksa_caching)
params.pmkid = sta->sae->pmkid;
hostapd_drv_send_external_auth_status(hapd, &params);
}
void sae_accept_sta(struct hostapd_data *hapd, struct sta_info *sta)
{
#ifndef CONFIG_NO_VLAN
struct vlan_description vlan_desc;
if (sta->sae->tmp && sta->sae->tmp->vlan_id > 0) {
wpa_printf(MSG_DEBUG, "SAE: Assign STA " MACSTR
" to VLAN ID %d",
MAC2STR(sta->addr), sta->sae->tmp->vlan_id);
os_memset(&vlan_desc, 0, sizeof(vlan_desc));
vlan_desc.notempty = 1;
vlan_desc.untagged = sta->sae->tmp->vlan_id;
if (!hostapd_vlan_valid(hapd->conf->vlan, &vlan_desc)) {
wpa_printf(MSG_INFO,
"Invalid VLAN ID %d in sae_password",
sta->sae->tmp->vlan_id);
return;
}
if (ap_sta_set_vlan(hapd, sta, &vlan_desc) < 0 ||
ap_sta_bind_vlan(hapd, sta) < 0) {
wpa_printf(MSG_INFO,
"Failed to assign VLAN ID %d from sae_password to "
MACSTR, sta->sae->tmp->vlan_id,
MAC2STR(sta->addr));
return;
}
}
#endif /* CONFIG_NO_VLAN */
sta->flags |= WLAN_STA_AUTH;
sta->auth_alg = WLAN_AUTH_SAE;
mlme_authenticate_indication(hapd, sta);
wpa_auth_sm_event(sta->wpa_sm, WPA_AUTH);
sae_set_state(sta, SAE_ACCEPTED, "Accept Confirm");
crypto_bignum_deinit(sta->sae->peer_commit_scalar_accepted, 0);
sta->sae->peer_commit_scalar_accepted = sta->sae->peer_commit_scalar;
sta->sae->peer_commit_scalar = NULL;
wpa_auth_pmksa_add_sae(hapd->wpa_auth, sta->addr,
sta->sae->pmk, sta->sae->pmkid);
sae_sme_send_external_auth_status(hapd, sta, WLAN_STATUS_SUCCESS);
}
static int sae_sm_step(struct hostapd_data *hapd, struct sta_info *sta,
const u8 *bssid, u16 auth_transaction, u16 status_code,
int allow_reuse, int *sta_removed)
{
int ret;
*sta_removed = 0;
if (auth_transaction != 1 && auth_transaction != 2)
return WLAN_STATUS_UNSPECIFIED_FAILURE;
wpa_printf(MSG_DEBUG, "SAE: Peer " MACSTR " state=%s auth_trans=%u",
MAC2STR(sta->addr), sae_state_txt(sta->sae->state),
auth_transaction);
switch (sta->sae->state) {
case SAE_NOTHING:
if (auth_transaction == 1) {
if (sta->sae->tmp) {
sta->sae->h2e =
(status_code ==
WLAN_STATUS_SAE_HASH_TO_ELEMENT ||
status_code == WLAN_STATUS_SAE_PK);
sta->sae->pk =
status_code == WLAN_STATUS_SAE_PK;
}
ret = auth_sae_send_commit(hapd, sta, bssid,
!allow_reuse, status_code);
if (ret)
return ret;
sae_set_state(sta, SAE_COMMITTED, "Sent Commit");
if (sae_process_commit(sta->sae) < 0)
return WLAN_STATUS_UNSPECIFIED_FAILURE;
/*
* In mesh case, both Commit and Confirm are sent
* immediately. In infrastructure BSS, by default, only
* a single Authentication frame (Commit) is expected
* from the AP here and the second one (Confirm) will
* be sent once the STA has sent its second
* Authentication frame (Confirm). This behavior can be
* overridden with explicit configuration so that the
* infrastructure BSS case sends both frames together.
*/
if ((hapd->conf->mesh & MESH_ENABLED) ||
hapd->conf->sae_confirm_immediate) {
/*
* Send both Commit and Confirm immediately
* based on SAE finite state machine
* Nothing -> Confirm transition.
*/
ret = auth_sae_send_confirm(hapd, sta, bssid);
if (ret)
return ret;
sae_set_state(sta, SAE_CONFIRMED,
"Sent Confirm (mesh)");
} else {
/*
* For infrastructure BSS, send only the Commit
* message now to get alternating sequence of
* Authentication frames between the AP and STA.
* Confirm will be sent in
* Committed -> Confirmed/Accepted transition
* when receiving Confirm from STA.
*/
}
sta->sae->sync = 0;
sae_set_retransmit_timer(hapd, sta);
} else {
hostapd_logger(hapd, sta->addr,
HOSTAPD_MODULE_IEEE80211,
HOSTAPD_LEVEL_DEBUG,
"SAE confirm before commit");
}
break;
case SAE_COMMITTED:
sae_clear_retransmit_timer(hapd, sta);
if (auth_transaction == 1) {
if (sae_process_commit(sta->sae) < 0)
return WLAN_STATUS_UNSPECIFIED_FAILURE;
ret = auth_sae_send_confirm(hapd, sta, bssid);
if (ret)
return ret;
sae_set_state(sta, SAE_CONFIRMED, "Sent Confirm");
sta->sae->sync = 0;
sae_set_retransmit_timer(hapd, sta);
} else if (hapd->conf->mesh & MESH_ENABLED) {
/*
* In mesh case, follow SAE finite state machine and
* send Commit now, if sync count allows.
*/
if (sae_check_big_sync(hapd, sta))
return WLAN_STATUS_SUCCESS;
sta->sae->sync++;
ret = auth_sae_send_commit(hapd, sta, bssid, 0,
status_code);
if (ret)
return ret;
sae_set_retransmit_timer(hapd, sta);
} else {
/*
* For instructure BSS, send the postponed Confirm from
* Nothing -> Confirmed transition that was reduced to
* Nothing -> Committed above.
*/
ret = auth_sae_send_confirm(hapd, sta, bssid);
if (ret)
return ret;
sae_set_state(sta, SAE_CONFIRMED, "Sent Confirm");
/*
* Since this was triggered on Confirm RX, run another
* step to get to Accepted without waiting for
* additional events.
*/
return sae_sm_step(hapd, sta, bssid, auth_transaction,
WLAN_STATUS_SUCCESS, 0, sta_removed);
}
break;
case SAE_CONFIRMED:
sae_clear_retransmit_timer(hapd, sta);
if (auth_transaction == 1) {
if (sae_check_big_sync(hapd, sta))
return WLAN_STATUS_SUCCESS;
sta->sae->sync++;
ret = auth_sae_send_commit(hapd, sta, bssid, 1,
status_code);
if (ret)
return ret;
if (sae_process_commit(sta->sae) < 0)
return WLAN_STATUS_UNSPECIFIED_FAILURE;
ret = auth_sae_send_confirm(hapd, sta, bssid);
if (ret)
return ret;
sae_set_retransmit_timer(hapd, sta);
} else {
sta->sae->send_confirm = 0xffff;
sae_accept_sta(hapd, sta);
}
break;
case SAE_ACCEPTED:
if (auth_transaction == 1 &&
(hapd->conf->mesh & MESH_ENABLED)) {
wpa_printf(MSG_DEBUG, "SAE: remove the STA (" MACSTR
") doing reauthentication",
MAC2STR(sta->addr));
wpa_auth_pmksa_remove(hapd->wpa_auth, sta->addr);
ap_free_sta(hapd, sta);
*sta_removed = 1;
} else if (auth_transaction == 1) {
wpa_printf(MSG_DEBUG, "SAE: Start reauthentication");
ret = auth_sae_send_commit(hapd, sta, bssid, 1,
status_code);
if (ret)
return ret;
sae_set_state(sta, SAE_COMMITTED, "Sent Commit");
if (sae_process_commit(sta->sae) < 0)
return WLAN_STATUS_UNSPECIFIED_FAILURE;
sta->sae->sync = 0;
sae_set_retransmit_timer(hapd, sta);
} else {
if (sae_check_big_sync(hapd, sta))
return WLAN_STATUS_SUCCESS;
sta->sae->sync++;
ret = auth_sae_send_confirm(hapd, sta, bssid);
sae_clear_temp_data(sta->sae);
if (ret)
return ret;
}
break;
default:
wpa_printf(MSG_ERROR, "SAE: invalid state %d",
sta->sae->state);
return WLAN_STATUS_UNSPECIFIED_FAILURE;
}
return WLAN_STATUS_SUCCESS;
}
static void sae_pick_next_group(struct hostapd_data *hapd, struct sta_info *sta)
{
struct sae_data *sae = sta->sae;
int i, *groups = hapd->conf->sae_groups;
int default_groups[] = { 19, 0 };
if (sae->state != SAE_COMMITTED)
return;
wpa_printf(MSG_DEBUG, "SAE: Previously selected group: %d", sae->group);
if (!groups)
groups = default_groups;
for (i = 0; groups[i] > 0; i++) {
if (sae->group == groups[i])
break;
}
if (groups[i] <= 0) {
wpa_printf(MSG_DEBUG,
"SAE: Previously selected group not found from the current configuration");
return;
}
for (;;) {
i++;
if (groups[i] <= 0) {
wpa_printf(MSG_DEBUG,
"SAE: No alternative group enabled");
return;
}
if (sae_set_group(sae, groups[i]) < 0)
continue;
break;
}
wpa_printf(MSG_DEBUG, "SAE: Selected new group: %d", groups[i]);
}
static int sae_status_success(struct hostapd_data *hapd, u16 status_code)
{
int sae_pwe = hapd->conf->sae_pwe;
int id_in_use;
bool sae_pk = false;
id_in_use = hostapd_sae_pw_id_in_use(hapd->conf);
if (id_in_use == 2 && sae_pwe != 3)
sae_pwe = 1;
else if (id_in_use == 1 && sae_pwe == 0)
sae_pwe = 2;
#ifdef CONFIG_SAE_PK
sae_pk = hostapd_sae_pk_in_use(hapd->conf);
if (sae_pwe == 0 && sae_pk)
sae_pwe = 2;
#endif /* CONFIG_SAE_PK */
return ((sae_pwe == 0 || sae_pwe == 3) &&
status_code == WLAN_STATUS_SUCCESS) ||
(sae_pwe == 1 &&
(status_code == WLAN_STATUS_SAE_HASH_TO_ELEMENT ||
(sae_pk && status_code == WLAN_STATUS_SAE_PK))) ||
(sae_pwe == 2 &&
(status_code == WLAN_STATUS_SUCCESS ||
status_code == WLAN_STATUS_SAE_HASH_TO_ELEMENT ||
(sae_pk && status_code == WLAN_STATUS_SAE_PK)));
}
static int sae_is_group_enabled(struct hostapd_data *hapd, int group)
{
int *groups = hapd->conf->sae_groups;
int default_groups[] = { 19, 0 };
int i;
if (!groups)
groups = default_groups;
for (i = 0; groups[i] > 0; i++) {
if (groups[i] == group)
return 1;
}
return 0;
}
static int check_sae_rejected_groups(struct hostapd_data *hapd,
struct sae_data *sae)
{
const struct wpabuf *groups;
size_t i, count;
const u8 *pos;
if (!sae->tmp)
return 0;
groups = sae->tmp->peer_rejected_groups;
if (!groups)
return 0;
pos = wpabuf_head(groups);
count = wpabuf_len(groups) / 2;
for (i = 0; i < count; i++) {
int enabled;
u16 group;
group = WPA_GET_LE16(pos);
pos += 2;
enabled = sae_is_group_enabled(hapd, group);
wpa_printf(MSG_DEBUG, "SAE: Rejected group %u is %s",
group, enabled ? "enabled" : "disabled");
if (enabled)
return 1;
}
return 0;
}
static void handle_auth_sae(struct hostapd_data *hapd, struct sta_info *sta,
const struct ieee80211_mgmt *mgmt, size_t len,
u16 auth_transaction, u16 status_code)
{
int resp = WLAN_STATUS_SUCCESS;
struct wpabuf *data = NULL;
int *groups = hapd->conf->sae_groups;
int default_groups[] = { 19, 0 };
const u8 *pos, *end;
int sta_removed = 0;
bool success_status;
if (!groups)
groups = default_groups;
#ifdef CONFIG_TESTING_OPTIONS
if (hapd->conf->sae_reflection_attack && auth_transaction == 1) {
wpa_printf(MSG_DEBUG, "SAE: TESTING - reflection attack");
pos = mgmt->u.auth.variable;
end = ((const u8 *) mgmt) + len;
resp = status_code;
send_auth_reply(hapd, sta, mgmt->sa, mgmt->bssid, WLAN_AUTH_SAE,
auth_transaction, resp, pos, end - pos,
"auth-sae-reflection-attack");
goto remove_sta;
}
if (hapd->conf->sae_commit_override && auth_transaction == 1) {
wpa_printf(MSG_DEBUG, "SAE: TESTING - commit override");
send_auth_reply(hapd, sta, mgmt->sa, mgmt->bssid, WLAN_AUTH_SAE,
auth_transaction, resp,
wpabuf_head(hapd->conf->sae_commit_override),
wpabuf_len(hapd->conf->sae_commit_override),
"sae-commit-override");
goto remove_sta;
}
#endif /* CONFIG_TESTING_OPTIONS */
if (!sta->sae) {
if (auth_transaction != 1 ||
!sae_status_success(hapd, status_code)) {
wpa_printf(MSG_DEBUG, "SAE: Unexpected Status Code %u",
status_code);
resp = WLAN_STATUS_UNSPECIFIED_FAILURE;
goto reply;
}
sta->sae = os_zalloc(sizeof(*sta->sae));
if (!sta->sae) {
resp = -1;
goto remove_sta;
}
sae_set_state(sta, SAE_NOTHING, "Init");
sta->sae->sync = 0;
}
if (sta->mesh_sae_pmksa_caching) {
wpa_printf(MSG_DEBUG,
"SAE: Cancel use of mesh PMKSA caching because peer starts SAE authentication");
wpa_auth_pmksa_remove(hapd->wpa_auth, sta->addr);
sta->mesh_sae_pmksa_caching = 0;
}
if (auth_transaction == 1) {
const u8 *token = NULL;
size_t token_len = 0;
int allow_reuse = 0;
hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211,
HOSTAPD_LEVEL_DEBUG,
"start SAE authentication (RX commit, status=%u (%s))",
status_code, status2str(status_code));
if ((hapd->conf->mesh & MESH_ENABLED) &&
status_code == WLAN_STATUS_ANTI_CLOGGING_TOKEN_REQ &&
sta->sae->tmp) {
pos = mgmt->u.auth.variable;
end = ((const u8 *) mgmt) + len;
if (pos + sizeof(le16) > end) {
wpa_printf(MSG_ERROR,
"SAE: Too short anti-clogging token request");
resp = WLAN_STATUS_UNSPECIFIED_FAILURE;
goto reply;
}
resp = sae_group_allowed(sta->sae, groups,
WPA_GET_LE16(pos));
if (resp != WLAN_STATUS_SUCCESS) {
wpa_printf(MSG_ERROR,
"SAE: Invalid group in anti-clogging token request");
goto reply;
}
pos += sizeof(le16);
wpabuf_free(sta->sae->tmp->anti_clogging_token);
sta->sae->tmp->anti_clogging_token =
wpabuf_alloc_copy(pos, end - pos);
if (sta->sae->tmp->anti_clogging_token == NULL) {
wpa_printf(MSG_ERROR,
"SAE: Failed to alloc for anti-clogging token");
resp = WLAN_STATUS_UNSPECIFIED_FAILURE;
goto remove_sta;
}
/*
* IEEE Std 802.11-2012, 11.3.8.6.4: If the Status code
* is 76, a new Commit Message shall be constructed
* with the Anti-Clogging Token from the received
* Authentication frame, and the commit-scalar and
* COMMIT-ELEMENT previously sent.
*/
resp = auth_sae_send_commit(hapd, sta, mgmt->bssid, 0,
status_code);
if (resp != WLAN_STATUS_SUCCESS) {
wpa_printf(MSG_ERROR,
"SAE: Failed to send commit message");
goto remove_sta;
}
sae_set_state(sta, SAE_COMMITTED,
"Sent Commit (anti-clogging token case in mesh)");
sta->sae->sync = 0;
sae_set_retransmit_timer(hapd, sta);
return;
}
if ((hapd->conf->mesh & MESH_ENABLED) &&
status_code ==
WLAN_STATUS_FINITE_CYCLIC_GROUP_NOT_SUPPORTED &&
sta->sae->tmp) {
wpa_printf(MSG_DEBUG,
"SAE: Peer did not accept our SAE group");
sae_pick_next_group(hapd, sta);
goto remove_sta;
}
if (!sae_status_success(hapd, status_code))
goto remove_sta;
if (!(hapd->conf->mesh & MESH_ENABLED) &&
sta->sae->state == SAE_COMMITTED) {
/* This is needed in the infrastructure BSS case to
* address a sequence where a STA entry may remain in
* hostapd across two attempts to do SAE authentication
* by the same STA. The second attempt may end up trying
* to use a different group and that would not be
* allowed if we remain in Committed state with the
* previously set parameters. */
pos = mgmt->u.auth.variable;
end = ((const u8 *) mgmt) + len;
if (end - pos >= (int) sizeof(le16) &&
sae_group_allowed(sta->sae, groups,
WPA_GET_LE16(pos)) ==
WLAN_STATUS_SUCCESS) {
/* Do not waste resources deriving the same PWE
* again since the same group is reused. */
sae_set_state(sta, SAE_NOTHING,
"Allow previous PWE to be reused");
allow_reuse = 1;
} else {
sae_set_state(sta, SAE_NOTHING,
"Clear existing state to allow restart");
sae_clear_data(sta->sae);
}
}
resp = sae_parse_commit(sta->sae, mgmt->u.auth.variable,
((const u8 *) mgmt) + len -
mgmt->u.auth.variable, &token,
&token_len, groups, status_code ==
WLAN_STATUS_SAE_HASH_TO_ELEMENT ||
status_code == WLAN_STATUS_SAE_PK);
if (resp == SAE_SILENTLY_DISCARD) {
wpa_printf(MSG_DEBUG,
"SAE: Drop commit message from " MACSTR " due to reflection attack",
MAC2STR(sta->addr));
goto remove_sta;
}
if (resp == WLAN_STATUS_UNKNOWN_PASSWORD_IDENTIFIER) {
wpa_msg(hapd->msg_ctx, MSG_INFO,
WPA_EVENT_SAE_UNKNOWN_PASSWORD_IDENTIFIER
MACSTR, MAC2STR(sta->addr));
sae_clear_retransmit_timer(hapd, sta);
sae_set_state(sta, SAE_NOTHING,
"Unknown Password Identifier");
goto remove_sta;
}
if (token && check_sae_token(hapd, sta->addr, token, token_len)
< 0) {
wpa_printf(MSG_DEBUG, "SAE: Drop commit message with "
"incorrect token from " MACSTR,
MAC2STR(sta->addr));
resp = WLAN_STATUS_UNSPECIFIED_FAILURE;
goto remove_sta;
}
if (resp != WLAN_STATUS_SUCCESS)
goto reply;
if (check_sae_rejected_groups(hapd, sta->sae)) {
resp = WLAN_STATUS_UNSPECIFIED_FAILURE;
goto reply;
}
if (!token && use_sae_anti_clogging(hapd) && !allow_reuse) {
int h2e = 0;
wpa_printf(MSG_DEBUG,
"SAE: Request anti-clogging token from "
MACSTR, MAC2STR(sta->addr));
if (sta->sae->tmp)
h2e = sta->sae->h2e;
if (status_code == WLAN_STATUS_SAE_HASH_TO_ELEMENT ||
status_code == WLAN_STATUS_SAE_PK)
h2e = 1;
data = auth_build_token_req(hapd, sta->sae->group,
sta->addr, h2e);
resp = WLAN_STATUS_ANTI_CLOGGING_TOKEN_REQ;
if (hapd->conf->mesh & MESH_ENABLED)
sae_set_state(sta, SAE_NOTHING,
"Request anti-clogging token case in mesh");
goto reply;
}
resp = sae_sm_step(hapd, sta, mgmt->bssid, auth_transaction,
status_code, allow_reuse, &sta_removed);
} else if (auth_transaction == 2) {
hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211,
HOSTAPD_LEVEL_DEBUG,
"SAE authentication (RX confirm, status=%u (%s))",
status_code, status2str(status_code));
if (status_code != WLAN_STATUS_SUCCESS)
goto remove_sta;
if (sta->sae->state >= SAE_CONFIRMED ||
!(hapd->conf->mesh & MESH_ENABLED)) {
const u8 *var;
size_t var_len;
u16 peer_send_confirm;
var = mgmt->u.auth.variable;
var_len = ((u8 *) mgmt) + len - mgmt->u.auth.variable;
if (var_len < 2) {
resp = WLAN_STATUS_UNSPECIFIED_FAILURE;
goto reply;
}
peer_send_confirm = WPA_GET_LE16(var);
if (sta->sae->state == SAE_ACCEPTED &&
(peer_send_confirm <= sta->sae->rc ||
peer_send_confirm == 0xffff)) {
wpa_printf(MSG_DEBUG,
"SAE: Silently ignore unexpected Confirm from peer "
MACSTR
" (peer-send-confirm=%u Rc=%u)",
MAC2STR(sta->addr),
peer_send_confirm, sta->sae->rc);
return;
}
if (sae_check_confirm(sta->sae, var, var_len) < 0) {
resp = WLAN_STATUS_UNSPECIFIED_FAILURE;
goto reply;
}
sta->sae->rc = peer_send_confirm;
}
resp = sae_sm_step(hapd, sta, mgmt->bssid, auth_transaction,
status_code, 0, &sta_removed);
} else {
hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211,
HOSTAPD_LEVEL_DEBUG,
"unexpected SAE authentication transaction %u (status=%u (%s))",
auth_transaction, status_code,
status2str(status_code));
if (status_code != WLAN_STATUS_SUCCESS)
goto remove_sta;
resp = WLAN_STATUS_UNKNOWN_AUTH_TRANSACTION;
}
reply:
if (!sta_removed && resp != WLAN_STATUS_SUCCESS) {
pos = mgmt->u.auth.variable;
end = ((const u8 *) mgmt) + len;
/* Copy the Finite Cyclic Group field from the request if we
* rejected it as unsupported group. */
if (resp == WLAN_STATUS_FINITE_CYCLIC_GROUP_NOT_SUPPORTED &&
!data && end - pos >= 2)
data = wpabuf_alloc_copy(pos, 2);
sae_sme_send_external_auth_status(hapd, sta, resp);
send_auth_reply(hapd, sta, mgmt->sa, mgmt->bssid, WLAN_AUTH_SAE,
auth_transaction, resp,
data ? wpabuf_head(data) : (u8 *) "",
data ? wpabuf_len(data) : 0, "auth-sae");
}
remove_sta:
if (auth_transaction == 1)
success_status = sae_status_success(hapd, status_code);
else
success_status = status_code == WLAN_STATUS_SUCCESS;
if (!sta_removed && sta->added_unassoc &&
(resp != WLAN_STATUS_SUCCESS || !success_status)) {
hostapd_drv_sta_remove(hapd, sta->addr);
sta->added_unassoc = 0;
}
wpabuf_free(data);
}
/**
* auth_sae_init_committed - Send COMMIT and start SAE in committed state
* @hapd: BSS data for the device initiating the authentication
* @sta: the peer to which commit authentication frame is sent
*
* This function implements Init event handling (IEEE Std 802.11-2012,
* 11.3.8.6.3) in which initial COMMIT message is sent. Prior to calling, the
* sta->sae structure should be initialized appropriately via a call to
* sae_prepare_commit().
*/
int auth_sae_init_committed(struct hostapd_data *hapd, struct sta_info *sta)
{
int ret;
if (!sta->sae || !sta->sae->tmp)
return -1;
if (sta->sae->state != SAE_NOTHING)
return -1;
ret = auth_sae_send_commit(hapd, sta, hapd->own_addr, 0, -1);
if (ret)
return -1;
sae_set_state(sta, SAE_COMMITTED, "Init and sent commit");
sta->sae->sync = 0;
sae_set_retransmit_timer(hapd, sta);
return 0;
}
void auth_sae_process_commit(void *eloop_ctx, void *user_ctx)
{
struct hostapd_data *hapd = eloop_ctx;
struct hostapd_sae_commit_queue *q;
unsigned int queue_len;
q = dl_list_first(&hapd->sae_commit_queue,
struct hostapd_sae_commit_queue, list);
if (!q)
return;
wpa_printf(MSG_DEBUG,
"SAE: Process next available message from queue");
dl_list_del(&q->list);
handle_auth(hapd, (const struct ieee80211_mgmt *) q->msg, q->len,
q->rssi, 1);
os_free(q);
if (eloop_is_timeout_registered(auth_sae_process_commit, hapd, NULL))
return;
queue_len = dl_list_len(&hapd->sae_commit_queue);
eloop_register_timeout(0, queue_len * 10000, auth_sae_process_commit,
hapd, NULL);
}
static void auth_sae_queue(struct hostapd_data *hapd,
const struct ieee80211_mgmt *mgmt, size_t len,
int rssi)
{
struct hostapd_sae_commit_queue *q, *q2;
unsigned int queue_len;
const struct ieee80211_mgmt *mgmt2;
queue_len = dl_list_len(&hapd->sae_commit_queue);
if (queue_len >= 15) {
wpa_printf(MSG_DEBUG,
"SAE: No more room in message queue - drop the new frame from "
MACSTR, MAC2STR(mgmt->sa));
return;
}
wpa_printf(MSG_DEBUG, "SAE: Queue Authentication message from "
MACSTR " for processing (queue_len %u)", MAC2STR(mgmt->sa),
queue_len);
q = os_zalloc(sizeof(*q) + len);
if (!q)
return;
q->rssi = rssi;
q->len = len;
os_memcpy(q->msg, mgmt, len);
/* Check whether there is already a queued Authentication frame from the
* same station with the same transaction number and if so, replace that
* queue entry with the new one. This avoids issues with a peer that
* sends multiple times (e.g., due to frequent SAE retries). There is no
* point in us trying to process the old attempts after a new one has
* obsoleted them. */
dl_list_for_each(q2, &hapd->sae_commit_queue,
struct hostapd_sae_commit_queue, list) {
mgmt2 = (const struct ieee80211_mgmt *) q2->msg;
if (os_memcmp(mgmt->sa, mgmt2->sa, ETH_ALEN) == 0 &&
mgmt->u.auth.auth_transaction ==
mgmt2->u.auth.auth_transaction) {
wpa_printf(MSG_DEBUG,
"SAE: Replace queued message from same STA with same transaction number");
dl_list_add(&q2->list, &q->list);
dl_list_del(&q2->list);
os_free(q2);
goto queued;
}
}
/* No pending identical entry, so add to the end of the queue */
dl_list_add_tail(&hapd->sae_commit_queue, &q->list);
queued:
if (eloop_is_timeout_registered(auth_sae_process_commit, hapd, NULL))
return;
eloop_register_timeout(0, queue_len * 10000, auth_sae_process_commit,
hapd, NULL);
}
static int auth_sae_queued_addr(struct hostapd_data *hapd, const u8 *addr)
{
struct hostapd_sae_commit_queue *q;
const struct ieee80211_mgmt *mgmt;
dl_list_for_each(q, &hapd->sae_commit_queue,
struct hostapd_sae_commit_queue, list) {
mgmt = (const struct ieee80211_mgmt *) q->msg;
if (os_memcmp(addr, mgmt->sa, ETH_ALEN) == 0)
return 1;
}
return 0;
}
#endif /* CONFIG_SAE */
static u16 wpa_res_to_status_code(enum wpa_validate_result res)
{
switch (res) {
case WPA_IE_OK:
return WLAN_STATUS_SUCCESS;
case WPA_INVALID_IE:
return WLAN_STATUS_INVALID_IE;
case WPA_INVALID_GROUP:
return WLAN_STATUS_GROUP_CIPHER_NOT_VALID;
case WPA_INVALID_PAIRWISE:
return WLAN_STATUS_PAIRWISE_CIPHER_NOT_VALID;
case WPA_INVALID_AKMP:
return WLAN_STATUS_AKMP_NOT_VALID;
case WPA_NOT_ENABLED:
return WLAN_STATUS_INVALID_IE;
case WPA_ALLOC_FAIL:
return WLAN_STATUS_UNSPECIFIED_FAILURE;
case WPA_MGMT_FRAME_PROTECTION_VIOLATION:
return WLAN_STATUS_ROBUST_MGMT_FRAME_POLICY_VIOLATION;
case WPA_INVALID_MGMT_GROUP_CIPHER:
return WLAN_STATUS_CIPHER_REJECTED_PER_POLICY;
case WPA_INVALID_MDIE:
return WLAN_STATUS_INVALID_MDIE;
case WPA_INVALID_PROTO:
return WLAN_STATUS_INVALID_IE;
case WPA_INVALID_PMKID:
return WLAN_STATUS_INVALID_PMKID;
case WPA_DENIED_OTHER_REASON:
return WLAN_STATUS_ASSOC_DENIED_UNSPEC;
}
return WLAN_STATUS_INVALID_IE;
}
#ifdef CONFIG_FILS
static void handle_auth_fils_finish(struct hostapd_data *hapd,
struct sta_info *sta, u16 resp,
struct wpabuf *data, int pub);
void handle_auth_fils(struct hostapd_data *hapd, struct sta_info *sta,
const u8 *pos, size_t len, u16 auth_alg,
u16 auth_transaction, u16 status_code,
void (*cb)(struct hostapd_data *hapd,
struct sta_info *sta, u16 resp,
struct wpabuf *data, int pub))
{
u16 resp = WLAN_STATUS_SUCCESS;
const u8 *end;
struct ieee802_11_elems elems;
enum wpa_validate_result res;
struct wpa_ie_data rsn;
struct rsn_pmksa_cache_entry *pmksa = NULL;
if (auth_transaction != 1 || status_code != WLAN_STATUS_SUCCESS)
return;
end = pos + len;
wpa_hexdump(MSG_DEBUG, "FILS: Authentication frame fields",
pos, end - pos);
/* TODO: FILS PK */
#ifdef CONFIG_FILS_SK_PFS
if (auth_alg == WLAN_AUTH_FILS_SK_PFS) {
u16 group;
struct wpabuf *pub;
size_t elem_len;
/* Using FILS PFS */
/* Finite Cyclic Group */
if (end - pos < 2) {
wpa_printf(MSG_DEBUG,
"FILS: No room for Finite Cyclic Group");
resp = WLAN_STATUS_UNSPECIFIED_FAILURE;
goto fail;
}
group = WPA_GET_LE16(pos);
pos += 2;
if (group != hapd->conf->fils_dh_group) {
wpa_printf(MSG_DEBUG,
"FILS: Unsupported Finite Cyclic Group: %u (expected %u)",
group, hapd->conf->fils_dh_group);
resp = WLAN_STATUS_FINITE_CYCLIC_GROUP_NOT_SUPPORTED;
goto fail;
}
crypto_ecdh_deinit(sta->fils_ecdh);
sta->fils_ecdh = crypto_ecdh_init(group);
if (!sta->fils_ecdh) {
wpa_printf(MSG_INFO,
"FILS: Could not initialize ECDH with group %d",
group);
resp = WLAN_STATUS_FINITE_CYCLIC_GROUP_NOT_SUPPORTED;
goto fail;
}
pub = crypto_ecdh_get_pubkey(sta->fils_ecdh, 1);
if (!pub) {
wpa_printf(MSG_DEBUG,
"FILS: Failed to derive ECDH public key");
resp = WLAN_STATUS_UNSPECIFIED_FAILURE;
goto fail;
}
elem_len = wpabuf_len(pub);
wpabuf_free(pub);
/* Element */
if ((size_t) (end - pos) < elem_len) {
wpa_printf(MSG_DEBUG, "FILS: No room for Element");
resp = WLAN_STATUS_UNSPECIFIED_FAILURE;
goto fail;
}
wpabuf_free(sta->fils_g_sta);
sta->fils_g_sta = wpabuf_alloc_copy(pos, elem_len);
wpabuf_clear_free(sta->fils_dh_ss);
sta->fils_dh_ss = crypto_ecdh_set_peerkey(sta->fils_ecdh, 1,
pos, elem_len);
if (!sta->fils_dh_ss) {
wpa_printf(MSG_DEBUG, "FILS: ECDH operation failed");
resp = WLAN_STATUS_UNSPECIFIED_FAILURE;
goto fail;
}
wpa_hexdump_buf_key(MSG_DEBUG, "FILS: DH_SS", sta->fils_dh_ss);
pos += elem_len;
} else {
crypto_ecdh_deinit(sta->fils_ecdh);
sta->fils_ecdh = NULL;
wpabuf_clear_free(sta->fils_dh_ss);
sta->fils_dh_ss = NULL;
}
#endif /* CONFIG_FILS_SK_PFS */
wpa_hexdump(MSG_DEBUG, "FILS: Remaining IEs", pos, end - pos);
if (ieee802_11_parse_elems(pos, end - pos, &elems, 1) == ParseFailed) {
wpa_printf(MSG_DEBUG, "FILS: Could not parse elements");
resp = WLAN_STATUS_UNSPECIFIED_FAILURE;
goto fail;
}
/* RSNE */
wpa_hexdump(MSG_DEBUG, "FILS: RSN element",
elems.rsn_ie, elems.rsn_ie_len);
if (!elems.rsn_ie ||
wpa_parse_wpa_ie_rsn(elems.rsn_ie - 2, elems.rsn_ie_len + 2,
&rsn) < 0) {
wpa_printf(MSG_DEBUG, "FILS: No valid RSN element");
resp = WLAN_STATUS_UNSPECIFIED_FAILURE;
goto fail;
}
if (!sta->wpa_sm)
sta->wpa_sm = wpa_auth_sta_init(hapd->wpa_auth, sta->addr,
NULL);
if (!sta->wpa_sm) {
wpa_printf(MSG_DEBUG,
"FILS: Failed to initialize RSN state machine");
resp = WLAN_STATUS_UNSPECIFIED_FAILURE;
goto fail;
}
res = wpa_validate_wpa_ie(hapd->wpa_auth, sta->wpa_sm,
hapd->iface->freq,
elems.rsn_ie - 2, elems.rsn_ie_len + 2,
elems.rsnxe ? elems.rsnxe - 2 : NULL,
elems.rsnxe ? elems.rsnxe_len + 2 : 0,
elems.mdie, elems.mdie_len, NULL, 0);
resp = wpa_res_to_status_code(res);
if (resp != WLAN_STATUS_SUCCESS)
goto fail;
if (!elems.fils_nonce) {
wpa_printf(MSG_DEBUG, "FILS: No FILS Nonce field");
resp = WLAN_STATUS_UNSPECIFIED_FAILURE;
goto fail;
}
wpa_hexdump(MSG_DEBUG, "FILS: SNonce", elems.fils_nonce,
FILS_NONCE_LEN);
os_memcpy(sta->fils_snonce, elems.fils_nonce, FILS_NONCE_LEN);
/* PMKID List */
if (rsn.pmkid && rsn.num_pmkid > 0) {
u8 num;
const u8 *pmkid;
wpa_hexdump(MSG_DEBUG, "FILS: PMKID List",
rsn.pmkid, rsn.num_pmkid * PMKID_LEN);
pmkid = rsn.pmkid;
num = rsn.num_pmkid;
while (num) {
wpa_hexdump(MSG_DEBUG, "FILS: PMKID", pmkid, PMKID_LEN);
pmksa = wpa_auth_pmksa_get(hapd->wpa_auth, sta->addr,
pmkid);
if (pmksa)
break;
pmksa = wpa_auth_pmksa_get_fils_cache_id(hapd->wpa_auth,
sta->addr,
pmkid);
if (pmksa)
break;
pmkid += PMKID_LEN;
num--;
}
}
if (pmksa && wpa_auth_sta_key_mgmt(sta->wpa_sm) != pmksa->akmp) {
wpa_printf(MSG_DEBUG,
"FILS: Matching PMKSA cache entry has different AKMP (0x%x != 0x%x) - ignore",
wpa_auth_sta_key_mgmt(sta->wpa_sm), pmksa->akmp);
pmksa = NULL;
}
if (pmksa)
wpa_printf(MSG_DEBUG, "FILS: Found matching PMKSA cache entry");
/* FILS Session */
if (!elems.fils_session) {
wpa_printf(MSG_DEBUG, "FILS: No FILS Session element");
resp = WLAN_STATUS_UNSPECIFIED_FAILURE;
goto fail;
}
wpa_hexdump(MSG_DEBUG, "FILS: FILS Session", elems.fils_session,
FILS_SESSION_LEN);
os_memcpy(sta->fils_session, elems.fils_session, FILS_SESSION_LEN);
/* Wrapped Data */
if (elems.wrapped_data) {
wpa_hexdump(MSG_DEBUG, "FILS: Wrapped Data",
elems.wrapped_data,
elems.wrapped_data_len);
if (!pmksa) {
#ifndef CONFIG_NO_RADIUS
if (!sta->eapol_sm) {
sta->eapol_sm =
ieee802_1x_alloc_eapol_sm(hapd, sta);
}
wpa_printf(MSG_DEBUG,
"FILS: Forward EAP-Initiate/Re-auth to authentication server");
ieee802_1x_encapsulate_radius(
hapd, sta, elems.wrapped_data,
elems.wrapped_data_len);
sta->fils_pending_cb = cb;
wpa_printf(MSG_DEBUG,
"FILS: Will send Authentication frame once the response from authentication server is available");
sta->flags |= WLAN_STA_PENDING_FILS_ERP;
/* Calculate pending PMKID here so that we do not need
* to maintain a copy of the EAP-Initiate/Reauth
* message. */
if (fils_pmkid_erp(wpa_auth_sta_key_mgmt(sta->wpa_sm),
elems.wrapped_data,
elems.wrapped_data_len,
sta->fils_erp_pmkid) == 0)
sta->fils_erp_pmkid_set = 1;
return;
#else /* CONFIG_NO_RADIUS */
resp = WLAN_STATUS_UNSPECIFIED_FAILURE;
goto fail;
#endif /* CONFIG_NO_RADIUS */
}
}
fail:
if (cb) {
struct wpabuf *data;
int pub = 0;
data = prepare_auth_resp_fils(hapd, sta, &resp, pmksa, NULL,
NULL, 0, &pub);
if (!data) {
wpa_printf(MSG_DEBUG,
"%s: prepare_auth_resp_fils() returned failure",
__func__);
}
cb(hapd, sta, resp, data, pub);
}
}
static struct wpabuf *
prepare_auth_resp_fils(struct hostapd_data *hapd,
struct sta_info *sta, u16 *resp,
struct rsn_pmksa_cache_entry *pmksa,
struct wpabuf *erp_resp,
const u8 *msk, size_t msk_len,
int *is_pub)
{
u8 fils_nonce[FILS_NONCE_LEN];
size_t ielen;
struct wpabuf *data = NULL;
const u8 *ie;
u8 *ie_buf = NULL;
const u8 *pmk = NULL;
size_t pmk_len = 0;
u8 pmk_buf[PMK_LEN_MAX];
struct wpabuf *pub = NULL;
if (*resp != WLAN_STATUS_SUCCESS)
goto fail;
ie = wpa_auth_get_wpa_ie(hapd->wpa_auth, &ielen);
if (!ie) {
*resp = WLAN_STATUS_UNSPECIFIED_FAILURE;
goto fail;
}
if (pmksa) {
/* Add PMKID of the selected PMKSA into RSNE */
ie_buf = os_malloc(ielen + 2 + 2 + PMKID_LEN);
if (!ie_buf) {
*resp = WLAN_STATUS_UNSPECIFIED_FAILURE;
goto fail;
}
os_memcpy(ie_buf, ie, ielen);
if (wpa_insert_pmkid(ie_buf, &ielen, pmksa->pmkid) < 0) {
*resp = WLAN_STATUS_UNSPECIFIED_FAILURE;
goto fail;
}
ie = ie_buf;
}
if (random_get_bytes(fils_nonce, FILS_NONCE_LEN) < 0) {
*resp = WLAN_STATUS_UNSPECIFIED_FAILURE;
goto fail;
}
wpa_hexdump(MSG_DEBUG, "RSN: Generated FILS Nonce",
fils_nonce, FILS_NONCE_LEN);
#ifdef CONFIG_FILS_SK_PFS
if (sta->fils_dh_ss && sta->fils_ecdh) {
pub = crypto_ecdh_get_pubkey(sta->fils_ecdh, 1);
if (!pub) {
*resp = WLAN_STATUS_UNSPECIFIED_FAILURE;
goto fail;
}
}
#endif /* CONFIG_FILS_SK_PFS */
data = wpabuf_alloc(1000 + ielen + (pub ? wpabuf_len(pub) : 0));
if (!data) {
*resp = WLAN_STATUS_UNSPECIFIED_FAILURE;
goto fail;
}
/* TODO: FILS PK */
#ifdef CONFIG_FILS_SK_PFS
if (pub) {
/* Finite Cyclic Group */
wpabuf_put_le16(data, hapd->conf->fils_dh_group);
/* Element */
wpabuf_put_buf(data, pub);
}
#endif /* CONFIG_FILS_SK_PFS */
/* RSNE */
wpabuf_put_data(data, ie, ielen);
/* MDE when using FILS+FT (already included in ie,ielen with RSNE) */
#ifdef CONFIG_IEEE80211R_AP
if (wpa_key_mgmt_ft(wpa_auth_sta_key_mgmt(sta->wpa_sm))) {
/* FTE[R1KH-ID,R0KH-ID] when using FILS+FT */
int res;
int use_sha384 = wpa_key_mgmt_sha384(
wpa_auth_sta_key_mgmt(sta->wpa_sm));
res = wpa_auth_write_fte(hapd->wpa_auth, use_sha384,
wpabuf_put(data, 0),
wpabuf_tailroom(data));
if (res < 0) {
*resp = WLAN_STATUS_UNSPECIFIED_FAILURE;
goto fail;
}
wpabuf_put(data, res);
}
#endif /* CONFIG_IEEE80211R_AP */
/* FILS Nonce */
wpabuf_put_u8(data, WLAN_EID_EXTENSION); /* Element ID */
wpabuf_put_u8(data, 1 + FILS_NONCE_LEN); /* Length */
/* Element ID Extension */
wpabuf_put_u8(data, WLAN_EID_EXT_FILS_NONCE);
wpabuf_put_data(data, fils_nonce, FILS_NONCE_LEN);
/* FILS Session */
wpabuf_put_u8(data, WLAN_EID_EXTENSION); /* Element ID */
wpabuf_put_u8(data, 1 + FILS_SESSION_LEN); /* Length */
/* Element ID Extension */
wpabuf_put_u8(data, WLAN_EID_EXT_FILS_SESSION);
wpabuf_put_data(data, sta->fils_session, FILS_SESSION_LEN);
/* Wrapped Data */
if (!pmksa && erp_resp) {
wpabuf_put_u8(data, WLAN_EID_EXTENSION); /* Element ID */
wpabuf_put_u8(data, 1 + wpabuf_len(erp_resp)); /* Length */
/* Element ID Extension */
wpabuf_put_u8(data, WLAN_EID_EXT_WRAPPED_DATA);
wpabuf_put_buf(data, erp_resp);
if (fils_rmsk_to_pmk(wpa_auth_sta_key_mgmt(sta->wpa_sm),
msk, msk_len, sta->fils_snonce, fils_nonce,
sta->fils_dh_ss ?
wpabuf_head(sta->fils_dh_ss) : NULL,
sta->fils_dh_ss ?
wpabuf_len(sta->fils_dh_ss) : 0,
pmk_buf, &pmk_len)) {
wpa_printf(MSG_DEBUG, "FILS: Failed to derive PMK");
*resp = WLAN_STATUS_UNSPECIFIED_FAILURE;
wpabuf_free(data);
data = NULL;
goto fail;
}
pmk = pmk_buf;
/* Don't use DHss in PTK derivation if PMKSA caching is not
* used. */
wpabuf_clear_free(sta->fils_dh_ss);
sta->fils_dh_ss = NULL;
if (sta->fils_erp_pmkid_set) {
/* TODO: get PMKLifetime from WPA parameters */
unsigned int dot11RSNAConfigPMKLifetime = 43200;
int session_timeout;
session_timeout = dot11RSNAConfigPMKLifetime;
if (sta->session_timeout_set) {
struct os_reltime now, diff;
os_get_reltime(&now);
os_reltime_sub(&sta->session_timeout, &now,
&diff);
session_timeout = diff.sec;
}
sta->fils_erp_pmkid_set = 0;
wpa_auth_add_fils_pmk_pmkid(sta->wpa_sm, pmk, pmk_len,
sta->fils_erp_pmkid);
if (!hapd->conf->disable_pmksa_caching &&
wpa_auth_pmksa_add2(
hapd->wpa_auth, sta->addr,
pmk, pmk_len,
sta->fils_erp_pmkid,
session_timeout,
wpa_auth_sta_key_mgmt(sta->wpa_sm)) < 0) {
wpa_printf(MSG_ERROR,
"FILS: Failed to add PMKSA cache entry based on ERP");
}
}
} else if (pmksa) {
pmk = pmksa->pmk;
pmk_len = pmksa->pmk_len;
}
if (!pmk) {
wpa_printf(MSG_DEBUG, "FILS: No PMK available");
*resp = WLAN_STATUS_UNSPECIFIED_FAILURE;
wpabuf_free(data);
data = NULL;
goto fail;
}
if (fils_auth_pmk_to_ptk(sta->wpa_sm, pmk, pmk_len,
sta->fils_snonce, fils_nonce,
sta->fils_dh_ss ?
wpabuf_head(sta->fils_dh_ss) : NULL,
sta->fils_dh_ss ?
wpabuf_len(sta->fils_dh_ss) : 0,
sta->fils_g_sta, pub) < 0) {
*resp = WLAN_STATUS_UNSPECIFIED_FAILURE;
wpabuf_free(data);
data = NULL;
goto fail;
}
fail:
if (is_pub)
*is_pub = pub != NULL;
os_free(ie_buf);
wpabuf_free(pub);
wpabuf_clear_free(sta->fils_dh_ss);
sta->fils_dh_ss = NULL;
#ifdef CONFIG_FILS_SK_PFS
crypto_ecdh_deinit(sta->fils_ecdh);
sta->fils_ecdh = NULL;
#endif /* CONFIG_FILS_SK_PFS */
return data;
}
static void handle_auth_fils_finish(struct hostapd_data *hapd,
struct sta_info *sta, u16 resp,
struct wpabuf *data, int pub)
{
u16 auth_alg;
auth_alg = (pub ||
resp == WLAN_STATUS_FINITE_CYCLIC_GROUP_NOT_SUPPORTED) ?
WLAN_AUTH_FILS_SK_PFS : WLAN_AUTH_FILS_SK;
send_auth_reply(hapd, sta, sta->addr, hapd->own_addr, auth_alg, 2, resp,
data ? wpabuf_head(data) : (u8 *) "",
data ? wpabuf_len(data) : 0, "auth-fils-finish");
wpabuf_free(data);
if (resp == WLAN_STATUS_SUCCESS) {
hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211,
HOSTAPD_LEVEL_DEBUG,
"authentication OK (FILS)");
sta->flags |= WLAN_STA_AUTH;
wpa_auth_sm_event(sta->wpa_sm, WPA_AUTH);
sta->auth_alg = pub ? WLAN_AUTH_FILS_SK_PFS : WLAN_AUTH_FILS_SK;
mlme_authenticate_indication(hapd, sta);
}
}
void ieee802_11_finish_fils_auth(struct hostapd_data *hapd,
struct sta_info *sta, int success,
struct wpabuf *erp_resp,
const u8 *msk, size_t msk_len)
{
u16 resp;
u32 flags = sta->flags;
sta->flags &= ~(WLAN_STA_PENDING_FILS_ERP |
WLAN_STA_PENDING_PASN_FILS_ERP);
resp = success ? WLAN_STATUS_SUCCESS : WLAN_STATUS_UNSPECIFIED_FAILURE;
if (flags & WLAN_STA_PENDING_FILS_ERP) {
struct wpabuf *data;
int pub = 0;
if (!sta->fils_pending_cb)
return;
data = prepare_auth_resp_fils(hapd, sta, &resp, NULL, erp_resp,
msk, msk_len, &pub);
if (!data) {
wpa_printf(MSG_DEBUG,
"%s: prepare_auth_resp_fils() failure",
__func__);
}
sta->fils_pending_cb(hapd, sta, resp, data, pub);
#ifdef CONFIG_PASN
} else if (flags & WLAN_STA_PENDING_PASN_FILS_ERP) {
pasn_fils_auth_resp(hapd, sta, resp, erp_resp,
msk, msk_len);
#endif /* CONFIG_PASN */
}
}
#endif /* CONFIG_FILS */
static int ieee802_11_allowed_address(struct hostapd_data *hapd, const u8 *addr,
const u8 *msg, size_t len,
struct radius_sta *info)
{
int res;
res = hostapd_allowed_address(hapd, addr, msg, len, info, 0);
if (res == HOSTAPD_ACL_REJECT) {
wpa_printf(MSG_DEBUG, "Station " MACSTR
" not allowed to authenticate",
MAC2STR(addr));
return HOSTAPD_ACL_REJECT;
}
if (res == HOSTAPD_ACL_PENDING) {
wpa_printf(MSG_DEBUG, "Authentication frame from " MACSTR
" waiting for an external authentication",
MAC2STR(addr));
/* Authentication code will re-send the authentication frame
* after it has received (and cached) information from the
* external source. */
return HOSTAPD_ACL_PENDING;
}
return res;
}
static int
ieee802_11_set_radius_info(struct hostapd_data *hapd, struct sta_info *sta,
int res, struct radius_sta *info)
{
u32 session_timeout = info->session_timeout;
u32 acct_interim_interval = info->acct_interim_interval;
struct vlan_description *vlan_id = &info->vlan_id;
struct hostapd_sta_wpa_psk_short *psk = info->psk;
char *identity = info->identity;
char *radius_cui = info->radius_cui;
if (vlan_id->notempty &&
!hostapd_vlan_valid(hapd->conf->vlan, vlan_id)) {
hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_RADIUS,
HOSTAPD_LEVEL_INFO,
"Invalid VLAN %d%s received from RADIUS server",
vlan_id->untagged,
vlan_id->tagged[0] ? "+" : "");
return -1;
}
if (ap_sta_set_vlan(hapd, sta, vlan_id) < 0)
return -1;
if (sta->vlan_id)
hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_RADIUS,
HOSTAPD_LEVEL_INFO, "VLAN ID %d", sta->vlan_id);
hostapd_free_psk_list(sta->psk);
if (hapd->conf->wpa_psk_radius != PSK_RADIUS_IGNORED)
hostapd_copy_psk_list(&sta->psk, psk);
else
sta->psk = NULL;
os_free(sta->identity);
if (identity)
sta->identity = os_strdup(identity);
else
sta->identity = NULL;
os_free(sta->radius_cui);
if (radius_cui)
sta->radius_cui = os_strdup(radius_cui);
else
sta->radius_cui = NULL;
if (hapd->conf->acct_interim_interval == 0 && acct_interim_interval)
sta->acct_interim_interval = acct_interim_interval;
if (res == HOSTAPD_ACL_ACCEPT_TIMEOUT) {
sta->session_timeout_set = 1;
os_get_reltime(&sta->session_timeout);
sta->session_timeout.sec += session_timeout;
ap_sta_session_timeout(hapd, sta, session_timeout);
} else {
sta->session_timeout_set = 0;
ap_sta_no_session_timeout(hapd, sta);
}
return 0;
}
#ifdef CONFIG_PASN
#ifdef CONFIG_SAE
static int pasn_wd_handle_sae_commit(struct hostapd_data *hapd,
struct sta_info *sta,
struct wpabuf *wd)
{
struct pasn_data *pasn = sta->pasn;
const char *password = NULL;
const u8 *data;
size_t buf_len;
u16 res, alg, seq, status;
int groups[] = { pasn->group, 0 };
int ret;
if (!wd)
return -1;
data = wpabuf_head_u8(wd);
buf_len = wpabuf_len(wd);
if (buf_len < 6) {
wpa_printf(MSG_DEBUG, "PASN: SAE buffer too short. len=%lu",
buf_len);
return -1;
}
alg = WPA_GET_LE16(data);
seq = WPA_GET_LE16(data + 2);
status = WPA_GET_LE16(data + 4);
wpa_printf(MSG_DEBUG, "PASN: SAE commit: alg=%u, seq=%u, status=%u",
alg, seq, status);
/* TODO: SAE H2E */
if (alg != WLAN_AUTH_SAE || seq != 1 || status != WLAN_STATUS_SUCCESS) {
wpa_printf(MSG_DEBUG, "PASN: Dropping peer SAE commit");
return -1;
}
sae_clear_data(&pasn->sae);
pasn->sae.state = SAE_NOTHING;
ret = sae_set_group(&pasn->sae, pasn->group);
if (ret) {
wpa_printf(MSG_DEBUG, "PASN: Failed to set SAE group");
return -1;
}
password = sae_get_password(hapd, sta, NULL, NULL, NULL, NULL);
if (!password) {
wpa_printf(MSG_DEBUG, "PASN: No SAE password found");
return -1;
}
ret = sae_prepare_commit(hapd->own_addr, sta->addr,
(const u8 *) password, os_strlen(password), 0,
&pasn->sae);
if (ret) {
wpa_printf(MSG_DEBUG, "PASN: Failed to prepare SAE commit");
return -1;
}
res = sae_parse_commit(&pasn->sae, data + 6, buf_len - 6, NULL, 0,
groups, 0);
if (res != WLAN_STATUS_SUCCESS) {
wpa_printf(MSG_DEBUG, "PASN: Failed parsing SAE commit");
return -1;
}
/* Process the commit message and derive the PMK */
ret = sae_process_commit(&pasn->sae);
if (ret) {
wpa_printf(MSG_DEBUG, "SAE: Failed to process peer commit");
return -1;
}
pasn->sae.state = SAE_COMMITTED;
return 0;
}
static int pasn_wd_handle_sae_confirm(struct hostapd_data *hapd,
struct sta_info *sta,
struct wpabuf *wd)
{
struct pasn_data *pasn = sta->pasn;
const u8 *data;
size_t buf_len;
u16 res, alg, seq, status;
if (!wd)
return -1;
data = wpabuf_head_u8(wd);
buf_len = wpabuf_len(wd);
if (buf_len < 6) {
wpa_printf(MSG_DEBUG, "PASN: SAE buffer too short. len=%lu",
buf_len);
return -1;
}
alg = WPA_GET_LE16(data);
seq = WPA_GET_LE16(data + 2);
status = WPA_GET_LE16(data + 4);
wpa_printf(MSG_DEBUG, "PASN: SAE confirm: alg=%u, seq=%u, status=%u",
alg, seq, status);
if (alg != WLAN_AUTH_SAE || seq != 2 || status != WLAN_STATUS_SUCCESS) {
wpa_printf(MSG_DEBUG, "PASN: Dropping peer SAE confirm");
return -1;
}
res = sae_check_confirm(&pasn->sae, data + 6, buf_len - 6);
if (res != WLAN_STATUS_SUCCESS) {
wpa_printf(MSG_DEBUG, "PASN: SAE failed checking confirm");
return -1;
}
pasn->sae.state = SAE_ACCEPTED;
/*
* TODO: Based on on IEEE P802.11az/D2.6, the PMKSA derived with
* PASN/SAE should only be allowed with future PASN only. For now do not
* restrict this only for PASN.
*/
wpa_auth_pmksa_add_sae(hapd->wpa_auth, sta->addr,
pasn->sae.pmk, pasn->sae.pmkid);
return 0;
}
static struct wpabuf * pasn_get_sae_wd(struct hostapd_data *hapd,
struct sta_info *sta)
{
struct pasn_data *pasn = sta->pasn;
struct wpabuf *buf = NULL;
u8 *len_ptr;
size_t len;
/* Need to add the entire Authentication frame body */
buf = wpabuf_alloc(8 + SAE_COMMIT_MAX_LEN + 8 + SAE_CONFIRM_MAX_LEN);
if (!buf) {
wpa_printf(MSG_DEBUG, "PASN: Failed to allocate SAE buffer");
return NULL;
}
/* Need to add the entire authentication frame body for the commit */
len_ptr = wpabuf_put(buf, 2);
wpabuf_put_le16(buf, WLAN_AUTH_SAE);
wpabuf_put_le16(buf, 1);
wpabuf_put_le16(buf, WLAN_STATUS_SUCCESS);
/* Write the actual commit and update the length accordingly */
sae_write_commit(&pasn->sae, buf, NULL, 0);
len = wpabuf_len(buf);
WPA_PUT_LE16(len_ptr, len - 2);
/* Need to add the entire Authentication frame body for the confirm */
len_ptr = wpabuf_put(buf, 2);
wpabuf_put_le16(buf, WLAN_AUTH_SAE);
wpabuf_put_le16(buf, 2);
wpabuf_put_le16(buf, WLAN_STATUS_SUCCESS);
sae_write_confirm(&pasn->sae, buf);
WPA_PUT_LE16(len_ptr, wpabuf_len(buf) - len - 2);
pasn->sae.state = SAE_CONFIRMED;
return buf;
}
#endif /* CONFIG_SAE */
#ifdef CONFIG_FILS
static struct wpabuf * pasn_get_fils_wd(struct hostapd_data *hapd,
struct sta_info *sta)
{
struct pasn_data *pasn = sta->pasn;
struct pasn_fils_data *fils = &pasn->fils;
struct wpabuf *buf = NULL;
if (!fils->erp_resp) {
wpa_printf(MSG_DEBUG, "PASN: FILS: Missing erp_resp");
return NULL;
}
buf = wpabuf_alloc(1500);
if (!buf)
return NULL;
/* Add the authentication algorithm */
wpabuf_put_le16(buf, WLAN_AUTH_FILS_SK);
/* Authentication Transaction seq# */
wpabuf_put_le16(buf, 2);
/* Status Code */
wpabuf_put_le16(buf, WLAN_STATUS_SUCCESS);
/* Own RSNE */
wpa_pasn_add_rsne(buf, NULL, pasn->akmp, pasn->cipher);
/* FILS Nonce */
wpabuf_put_u8(buf, WLAN_EID_EXTENSION);
wpabuf_put_u8(buf, 1 + FILS_NONCE_LEN);
wpabuf_put_u8(buf, WLAN_EID_EXT_FILS_NONCE);
wpabuf_put_data(buf, fils->anonce, FILS_NONCE_LEN);
/* FILS Session */
wpabuf_put_u8(buf, WLAN_EID_EXTENSION);
wpabuf_put_u8(buf, 1 + FILS_SESSION_LEN);
wpabuf_put_u8(buf, WLAN_EID_EXT_FILS_SESSION);
wpabuf_put_data(buf, fils->session, FILS_SESSION_LEN);
/* Wrapped Data */
wpabuf_put_u8(buf, WLAN_EID_EXTENSION);
wpabuf_put_u8(buf, 1 + wpabuf_len(fils->erp_resp));
wpabuf_put_u8(buf, WLAN_EID_EXT_WRAPPED_DATA);
wpabuf_put_buf(buf, fils->erp_resp);
return buf;
}
static void pasn_fils_auth_resp(struct hostapd_data *hapd,
struct sta_info *sta, u16 status,
struct wpabuf *erp_resp,
const u8 *msk, size_t msk_len)
{
struct pasn_data *pasn = sta->pasn;
struct pasn_fils_data *fils = &pasn->fils;
u8 pmk[PMK_LEN_MAX];
size_t pmk_len;
int ret;
wpa_printf(MSG_DEBUG, "PASN: FILS: Handle AS response - status=%u",
status);
if (status != WLAN_STATUS_SUCCESS)
goto fail;
if (!pasn->secret) {
wpa_printf(MSG_DEBUG, "PASN: FILS: Missing secret");
goto fail;
}
if (random_get_bytes(fils->anonce, FILS_NONCE_LEN) < 0) {
wpa_printf(MSG_DEBUG, "PASN: FILS: Failed to get ANonce");
goto fail;
}
wpa_hexdump(MSG_DEBUG, "RSN: Generated FILS ANonce",
fils->anonce, FILS_NONCE_LEN);
ret = fils_rmsk_to_pmk(pasn->akmp, msk, msk_len, fils->nonce,
fils->anonce, NULL, 0, pmk, &pmk_len);
if (ret) {
wpa_printf(MSG_DEBUG, "FILS: Failed to derive PMK");
goto fail;
}
ret = pasn_pmk_to_ptk(pmk, pmk_len, sta->addr, hapd->own_addr,
wpabuf_head(pasn->secret),
wpabuf_len(pasn->secret),
&sta->pasn->ptk, sta->pasn->akmp,
sta->pasn->cipher, WPA_KDK_MAX_LEN);
if (ret) {
wpa_printf(MSG_DEBUG, "PASN: FILS: Failed to derive PTK");
goto fail;
}
wpa_printf(MSG_DEBUG, "PASN: PTK successfully derived");
wpabuf_free(pasn->secret);
pasn->secret = NULL;
fils->erp_resp = erp_resp;
ret = handle_auth_pasn_resp(hapd, sta, NULL, WLAN_STATUS_SUCCESS);
fils->erp_resp = NULL;
if (ret) {
wpa_printf(MSG_DEBUG, "PASN: FILS: Failed to send response");
goto fail;
}
fils->state = PASN_FILS_STATE_COMPLETE;
return;
fail:
ap_free_sta(hapd, sta);
}
static int pasn_wd_handle_fils(struct hostapd_data *hapd, struct sta_info *sta,
struct wpabuf *wd)
{
struct pasn_data *pasn = sta->pasn;
struct pasn_fils_data *fils = &pasn->fils;
struct ieee802_11_elems elems;
struct wpa_ie_data rsne_data;
struct wpabuf *fils_wd;
const u8 *data;
size_t buf_len;
u16 alg, seq, status;
int ret;
if (fils->state != PASN_FILS_STATE_NONE) {
wpa_printf(MSG_DEBUG, "PASN: FILS: Not expecting wrapped data");
return -1;
}
if (!wd) {
wpa_printf(MSG_DEBUG, "PASN: FILS: No wrapped data");
return -1;
}
data = wpabuf_head_u8(wd);