esp-idf/components/wpa_supplicant/esp_supplicant/src/esp_wpa3.c

656 wiersze
20 KiB
C

/*
* SPDX-FileCopyrightText: 2019-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifdef CONFIG_WPA3_SAE
#include "common/sae.h"
#include "common/ieee802_11_defs.h"
#include "esp_wifi_driver.h"
#include "rsn_supp/wpa.h"
#include "ap/hostapd.h"
#include "ap/ieee802_11.h"
#include "ap/sta_info.h"
#include "esp_wpa3_i.h"
#include "endian.h"
#include "esp_hostap.h"
#include <inttypes.h>
static struct sae_pt *g_sae_pt;
static struct sae_data g_sae_data;
static struct wpabuf *g_sae_token = NULL;
static struct wpabuf *g_sae_commit = NULL;
static struct wpabuf *g_sae_confirm = NULL;
int g_allowed_groups[] = { IANA_SECP256R1, 0 };
static esp_err_t wpa3_build_sae_commit(u8 *bssid, size_t *sae_msg_len)
{
int default_group = IANA_SECP256R1;
u32 len = 0;
uint8_t use_pt = 0;
u8 own_addr[ETH_ALEN];
const u8 *pw = (const u8 *)esp_wifi_sta_get_prof_password_internal();
struct wifi_ssid *ssid = esp_wifi_sta_get_prof_ssid_internal();
uint8_t sae_pwe = esp_wifi_get_config_sae_pwe_h2e_internal(WIFI_IF_STA);
char sae_pwd_id[SAE_H2E_IDENTIFIER_LEN + 1] = {0};
bool valid_pwd_id = false;
const u8 *rsnxe;
u8 rsnxe_capa = 0;
if (wpa_key_mgmt_sae_ext_key(gWpaSm.key_mgmt)) {
use_pt = 1;
}
rsnxe = esp_wifi_sta_get_rsnxe(bssid);
if (rsnxe && rsnxe[1] >= 1) {
rsnxe_capa = rsnxe[2];
}
#ifdef CONFIG_SAE_PK
bool use_pk = false;
uint8_t sae_pk_mode = esp_wifi_sta_get_config_sae_pk_internal();
if ((rsnxe_capa & BIT(WLAN_RSNX_CAPAB_SAE_PK)) &&
sae_pk_mode != WPA3_SAE_PK_MODE_DISABLED &&
((pw && sae_pk_valid_password((const char *)pw)))) {
use_pt = 1;
use_pk = true;
}
if (sae_pk_mode == WPA3_SAE_PK_MODE_ONLY && !use_pk) {
wpa_printf(MSG_DEBUG,
"SAE: Cannot use PK with the selected AP");
return ESP_FAIL;
}
#endif /* CONFIG_SAE_PK */
if (use_pt || sae_pwe == SAE_PWE_HASH_TO_ELEMENT ||
sae_pwe == SAE_PWE_BOTH) {
use_pt = !!(rsnxe_capa & BIT(WLAN_RSNX_CAPAB_SAE_H2E));
if ((sae_pwe == SAE_PWE_HASH_TO_ELEMENT ||
wpa_key_mgmt_sae_ext_key(gWpaSm.key_mgmt)
#ifdef CONFIG_SAE_PK
|| (use_pk && sae_pk_mode == WPA3_SAE_PK_MODE_ONLY)
#endif /* CONFIG_SAE_PK */
) && !use_pt) {
wpa_printf(MSG_DEBUG,
"SAE: Cannot use H2E with the selected AP");
return ESP_FAIL;
}
}
if (use_pt != 0) {
memcpy(sae_pwd_id, esp_wifi_sta_get_sae_identifier_internal(), SAE_H2E_IDENTIFIER_LEN);
if (os_strlen(sae_pwd_id) > 0) {
valid_pwd_id = true;
}
}
if (use_pt && !g_sae_pt) {
g_sae_pt = sae_derive_pt(g_allowed_groups, ssid->ssid, ssid->len, pw, strlen((const char *)pw), valid_pwd_id ? sae_pwd_id : NULL);
}
if (wpa_sta_cur_pmksa_matches_akm()) {
wpa_printf(MSG_INFO, "wpa3: Skip SAE and use cached PMK instead");
*sae_msg_len = 0;
return ESP_FAIL;
}
if (g_sae_commit) {
wpabuf_free(g_sae_commit);
g_sae_commit = NULL;
}
if (g_sae_token) {
len = wpabuf_len(g_sae_token);
goto reuse_data;
}
memset(&g_sae_data, 0, sizeof(g_sae_data));
if (sae_set_group(&g_sae_data, default_group)) {
wpa_printf(MSG_ERROR, "wpa3: could not set SAE group %d", default_group);
return ESP_FAIL;
}
g_sae_data.akmp = gWpaSm.key_mgmt;
esp_wifi_get_macaddr_internal(WIFI_IF_STA, own_addr);
if (!bssid) {
wpa_printf(MSG_ERROR, "wpa3: cannot prepare SAE commit with no BSSID!");
return ESP_FAIL;
}
if (use_pt &&
sae_prepare_commit_pt(&g_sae_data, g_sae_pt,
own_addr, bssid, NULL, NULL) < 0) {
wpa_printf(MSG_ERROR, "wpa3: failed to prepare SAE commit!");
return ESP_FAIL;
}
if (!use_pt &&
sae_prepare_commit(own_addr, bssid, pw,
strlen((const char *)pw),
&g_sae_data) < 0) {
wpa_printf(MSG_ERROR, "wpa3: failed to prepare SAE commit!");
return ESP_FAIL;
}
#ifdef CONFIG_SAE_PK
if (g_sae_data.tmp && use_pt && use_pk) {
g_sae_data.pk = 1;
os_memcpy(g_sae_data.tmp->own_addr, own_addr, ETH_ALEN);
os_memcpy(g_sae_data.tmp->peer_addr, bssid, ETH_ALEN);
sae_pk_set_password(&g_sae_data, (const char*) pw);
}
#endif
reuse_data:
len += SAE_COMMIT_MAX_LEN;
g_sae_commit = wpabuf_alloc(len);
if (!g_sae_commit) {
wpa_printf(MSG_ERROR, "wpa3: failed to allocate buffer for commit msg");
return ESP_FAIL;
}
if (sae_write_commit(&g_sae_data, g_sae_commit, g_sae_token, valid_pwd_id ? sae_pwd_id : NULL) != ESP_OK) {
wpa_printf(MSG_ERROR, "wpa3: failed to write SAE commit msg");
wpabuf_free(g_sae_commit);
g_sae_commit = NULL;
return ESP_FAIL;
}
if (g_sae_token) {
wpabuf_free(g_sae_token);
g_sae_token = NULL;
}
g_sae_data.state = SAE_COMMITTED;
return ESP_OK;
}
static esp_err_t wpa3_build_sae_confirm(void)
{
if (g_sae_data.state != SAE_COMMITTED) {
return ESP_FAIL;
}
if (g_sae_confirm) {
wpabuf_free(g_sae_confirm);
g_sae_confirm = NULL;
}
g_sae_confirm = wpabuf_alloc(SAE_COMMIT_MAX_LEN);
if (!g_sae_confirm) {
wpa_printf(MSG_ERROR, "wpa3: failed to allocate buffer for confirm msg");
return ESP_FAIL;
}
if (sae_write_confirm(&g_sae_data, g_sae_confirm) != ESP_OK) {
wpa_printf(MSG_ERROR, "wpa3: failed to write SAE confirm msg");
wpabuf_free(g_sae_confirm);
g_sae_confirm = NULL;
return ESP_FAIL;
}
g_sae_data.state = SAE_CONFIRMED;
return ESP_OK;
}
void esp_wpa3_free_sae_data(void)
{
if (g_sae_commit) {
wpabuf_free(g_sae_commit);
g_sae_commit = NULL;
}
if (g_sae_confirm) {
wpabuf_free(g_sae_confirm);
g_sae_confirm = NULL;
}
sae_clear_data(&g_sae_data);
if (g_sae_pt) {
sae_deinit_pt(g_sae_pt);
g_sae_pt = NULL;
}
}
static u8 *wpa3_build_sae_msg(u8 *bssid, u32 sae_msg_type, size_t *sae_msg_len)
{
u8 *buf = NULL;
switch (sae_msg_type) {
case SAE_MSG_COMMIT:
/* Do not go for SAE when WPS is ongoing */
if (esp_wifi_get_wps_status_internal() != WPS_STATUS_DISABLE) {
*sae_msg_len = 0;
return NULL;
}
if (ESP_OK != wpa3_build_sae_commit(bssid, sae_msg_len)) {
return NULL;
}
*sae_msg_len = wpabuf_len(g_sae_commit);
buf = wpabuf_mhead_u8(g_sae_commit);
break;
case SAE_MSG_CONFIRM:
if (ESP_OK != wpa3_build_sae_confirm()) {
return NULL;
}
*sae_msg_len = wpabuf_len(g_sae_confirm);
buf = wpabuf_mhead_u8(g_sae_confirm);
break;
default:
break;
}
return buf;
}
static int wpa3_parse_sae_commit(u8 *buf, u32 len, u16 status)
{
int ret;
if (g_sae_data.state != SAE_COMMITTED) {
wpa_printf(MSG_DEBUG, "wpa3: Discarding commit frame received in state %d", g_sae_data.state);
return ESP_ERR_WIFI_DISCARD;
}
if (status == WLAN_STATUS_ANTI_CLOGGING_TOKEN_REQ) {
if (g_sae_token) {
wpabuf_free(g_sae_token);
}
if (g_sae_data.h2e) {
if ((buf[2] != WLAN_EID_EXTENSION) ||
(buf[3] == 0) ||
(buf[3] > len - 4) ||
(buf[4] != WLAN_EID_EXT_ANTI_CLOGGING_TOKEN)) {
wpa_printf(MSG_ERROR, "Invalid SAE anti-clogging token container header");
return ESP_FAIL;
}
g_sae_token = wpabuf_alloc_copy(buf + 5, len - 5);
} else {
g_sae_token = wpabuf_alloc_copy(buf + 2, len - 2);
}
return ESP_OK;
}
ret = sae_parse_commit(&g_sae_data, buf, len, NULL, 0, g_allowed_groups,
(status == WLAN_STATUS_SAE_HASH_TO_ELEMENT || status == WLAN_STATUS_SAE_PK));
if (ret == SAE_SILENTLY_DISCARD) {
wpa_printf(MSG_DEBUG, "wpa3: Discarding commit frame due to reflection attack");
return ESP_ERR_WIFI_DISCARD;
} else if (ret) {
wpa_printf(MSG_ERROR, "wpa3: could not parse commit(%d)", ret);
return ret;
}
ret = sae_process_commit(&g_sae_data);
if (ret) {
wpa_printf(MSG_ERROR, "wpa3: could not process commit(%d)", ret);
return ret;
}
return ESP_OK;
}
static int wpa3_parse_sae_confirm(u8 *buf, u32 len)
{
if (g_sae_data.state != SAE_CONFIRMED) {
wpa_printf(MSG_ERROR, "wpa3: failed to parse SAE commit in state(%d)!",
g_sae_data.state);
return ESP_FAIL;
}
if (sae_check_confirm(&g_sae_data, buf, len) != ESP_OK) {
wpa_printf(MSG_ERROR, "wpa3: failed to parse SAE confirm");
return ESP_FAIL;
}
g_sae_data.state = SAE_ACCEPTED;
wpa_set_pmk(g_sae_data.pmk, g_sae_data.pmk_len, g_sae_data.pmkid, true);
return ESP_OK;
}
static int wpa3_parse_sae_msg(u8 *buf, size_t len, u32 sae_msg_type, u16 status)
{
int ret = ESP_OK;
switch (sae_msg_type) {
case SAE_MSG_COMMIT:
ret = wpa3_parse_sae_commit(buf, len, status);
break;
case SAE_MSG_CONFIRM:
ret = wpa3_parse_sae_confirm(buf, len);
esp_wpa3_free_sae_data();
break;
default:
wpa_printf(MSG_ERROR, "wpa3: Invalid SAE msg type(%" PRId32 ")!", sae_msg_type);
ret = ESP_FAIL;
break;
}
return ret;
}
void esp_wifi_register_wpa3_cb(struct wpa_funcs *wpa_cb)
{
wpa_cb->wpa3_build_sae_msg = wpa3_build_sae_msg;
wpa_cb->wpa3_parse_sae_msg = wpa3_parse_sae_msg;
}
void esp_wifi_unregister_wpa3_cb(void)
{
extern struct wpa_funcs *wpa_cb;
wpa_cb->wpa3_build_sae_msg = NULL;
wpa_cb->wpa3_parse_sae_msg = NULL;
}
#endif /* CONFIG_WPA3_SAE */
#ifdef CONFIG_SAE
static TaskHandle_t g_wpa3_hostap_task_hdl = NULL;
static QueueHandle_t g_wpa3_hostap_evt_queue = NULL;
SemaphoreHandle_t g_wpa3_hostap_auth_api_lock = NULL;
int wpa3_hostap_post_evt(uint32_t evt_id, uint32_t data)
{
wpa3_hostap_auth_event_t *evt = os_zalloc(sizeof(wpa3_hostap_auth_event_t));
if (evt == NULL) {
return ESP_FAIL;
}
evt->id = evt_id;
evt->data = data;
if (g_wpa3_hostap_auth_api_lock) {
WPA3_HOSTAP_AUTH_API_LOCK();
if (g_wpa3_hostap_evt_queue == NULL) {
WPA3_HOSTAP_AUTH_API_UNLOCK();
os_free(evt);
return ESP_FAIL;
}
} else {
os_free(evt);
return ESP_FAIL;
}
if (evt->id == SIG_WPA3_RX_CONFIRM || evt->id == SIG_TASK_DEL) {
/* prioritising confirm for completing handshake for committed sta */
if (os_queue_send_to_front(g_wpa3_hostap_evt_queue, &evt, 0) != pdPASS) {
WPA3_HOSTAP_AUTH_API_UNLOCK();
os_free(evt);
return ESP_FAIL;
}
} else {
if (os_queue_send(g_wpa3_hostap_evt_queue, &evt, 0) != pdPASS) {
WPA3_HOSTAP_AUTH_API_UNLOCK();
os_free(evt);
return ESP_FAIL;
}
}
if (evt_id != SIG_TASK_DEL) {
/* For SIG_TASK_DEL, WPA3_HOSTAP_AUTH_API_UNLOCK will be after clean up of hostapd_data */
WPA3_HOSTAP_AUTH_API_UNLOCK();
}
return ESP_OK;
}
static void wpa3_process_rx_commit(wpa3_hostap_auth_event_t *evt)
{
struct hostapd_sae_commit_queue *frm;
struct hostapd_data *hapd = (struct hostapd_data *)esp_wifi_get_hostap_private_internal();
struct sta_info *sta = NULL;
int ret;
frm = dl_list_first(&hapd->sae_commit_queue,
struct hostapd_sae_commit_queue, list);
if (!frm) {
return;
}
dl_list_del(&frm->list);
wpa_printf(MSG_DEBUG, "SAE: Process next available message from queue");
sta = ap_get_sta(hapd, frm->bssid);
if (!sta) {
sta = ap_sta_add(hapd, frm->bssid);
if (!sta) {
wpa_printf(MSG_DEBUG, "ap_sta_add() failed");
ret = WLAN_STATUS_AP_UNABLE_TO_HANDLE_NEW_STA;
if (esp_send_sae_auth_reply(hapd, frm->bssid, frm->bssid, WLAN_AUTH_SAE,
frm->auth_transaction, ret, NULL,
0) != 0) {
wpa_printf(MSG_INFO, "esp_send_sae_auth_reply: send failed");
}
goto free;
}
}
if (sta->lock && os_semphr_take(sta->lock, 0)) {
sta->sae_commit_processing = true;
ret = handle_auth_sae(hapd, sta, frm->msg, frm->len, frm->bssid, frm->auth_transaction, frm->status);
if (sta->remove_pending) {
ap_free_sta(hapd, sta);
goto free;
}
sta->sae_commit_processing = false;
os_semphr_give(sta->lock);
uint16_t aid = 0;
if (ret != WLAN_STATUS_SUCCESS &&
ret != WLAN_STATUS_ANTI_CLOGGING_TOKEN_REQ) {
esp_wifi_ap_get_sta_aid(frm->bssid, &aid);
if (aid == 0) {
esp_wifi_ap_deauth_internal(frm->bssid, ret);
}
}
}
free:
os_free(frm);
}
static void wpa3_process_rx_confirm(wpa3_hostap_auth_event_t *evt)
{
struct hostapd_data *hapd = (struct hostapd_data *)esp_wifi_get_hostap_private_internal();
struct sta_info *sta = NULL;
int ret = WLAN_STATUS_SUCCESS;
struct sae_hostap_confirm_data *frm = (struct sae_hostap_confirm_data *)evt->data;
if (!frm) {
return;
}
sta = ap_get_sta(hapd, frm->bssid);
if (!sta) {
os_free(frm);
return;
}
if (sta->lock && os_semphr_take(sta->lock, 0)) {
ret = handle_auth_sae(hapd, sta, frm->msg, frm->len, frm->bssid, frm->auth_transaction, frm->status);
if (sta->remove_pending) {
ap_free_sta(hapd, sta);
goto done;
}
if (ret == WLAN_STATUS_SUCCESS) {
if (esp_wifi_ap_notify_node_sae_auth_done(frm->bssid) != true) {
ap_free_sta(hapd, sta);
goto done;
}
}
os_semphr_give(sta->lock);
if (ret != WLAN_STATUS_SUCCESS) {
uint16_t aid = 0;
esp_wifi_ap_get_sta_aid(frm->bssid, &aid);
if (aid == 0) {
esp_wifi_ap_deauth_internal(frm->bssid, ret);
}
}
}
done:
os_free(frm);
}
static void esp_wpa3_hostap_task(void *pvParameters)
{
wpa3_hostap_auth_event_t *evt;
bool task_del = false;
while (1) {
if (os_queue_recv(g_wpa3_hostap_evt_queue, &evt, portMAX_DELAY) == pdTRUE) {
switch (evt->id) {
case SIG_WPA3_RX_COMMIT: {
wpa3_process_rx_commit(evt);
break;
}
case SIG_WPA3_RX_CONFIRM: {
wpa3_process_rx_confirm(evt);
break;
}
case SIG_TASK_DEL:
task_del = true;
break;
default:
break;
}
os_free(evt);
if (task_del) {
break;
}
}
}
uint32_t items_in_queue = os_queue_msg_waiting(g_wpa3_hostap_evt_queue);
while (items_in_queue--) {
/* Free events posted to queue */
os_queue_recv(g_wpa3_hostap_evt_queue, &evt, portMAX_DELAY);
if (evt->id == SIG_WPA3_RX_CONFIRM) {
os_free((void *)evt->data);
}
os_free(evt);
}
os_queue_delete(g_wpa3_hostap_evt_queue);
g_wpa3_hostap_evt_queue = NULL;
if (g_wpa3_hostap_auth_api_lock) {
WPA3_HOSTAP_AUTH_API_UNLOCK();
}
/* At this point, task is deleted*/
os_task_delete(NULL);
}
int wpa3_hostap_auth_init(void *data)
{
if (g_wpa3_hostap_evt_queue) {
wpa_printf(MSG_ERROR, "esp_wpa3_hostap_task has already been initialised");
return ESP_OK;
}
if (g_wpa3_hostap_auth_api_lock == NULL) {
g_wpa3_hostap_auth_api_lock = os_semphr_create(1, 1);
if (!g_wpa3_hostap_auth_api_lock) {
wpa_printf(MSG_ERROR, "wpa3_hostap_auth_init: failed to create WPA3 hostap auth API lock");
return ESP_FAIL;
}
}
g_wpa3_hostap_evt_queue = os_queue_create(10, sizeof(wpa3_hostap_auth_event_t));
if (!g_wpa3_hostap_evt_queue) {
wpa_printf(MSG_ERROR, "wpa3_hostap_auth_init: failed to create queue");
return ESP_FAIL;
}
if (os_task_create(esp_wpa3_hostap_task, "esp_wpa3_hostap_task",
WPA3_HOSTAP_HANDLE_AUTH_TASK_STACK_SIZE, NULL,
WPA3_HOSTAP_HANDLE_AUTH_TASK_PRIORITY,
&g_wpa3_hostap_task_hdl) != pdPASS) {
wpa_printf(MSG_ERROR, "wpa3_hostap_auth_init: failed to create task");
os_queue_delete(g_wpa3_hostap_evt_queue);
return ESP_FAIL;
}
struct hostapd_data *hapd = (struct hostapd_data *)data;
dl_list_init(&hapd->sae_commit_queue);
return ESP_OK;
}
bool wpa3_hostap_auth_deinit(void)
{
if (wpa3_hostap_post_evt(SIG_TASK_DEL, 0) != 0) {
wpa_printf(MSG_DEBUG, "failed to send task delete event");
return false;
} else {
return true;
}
}
static int wpa3_hostap_handle_auth(u8 *buf, size_t len, u32 auth_transaction, u16 status, u8 *bssid)
{
struct hostapd_data *hapd = (struct hostapd_data *)esp_wifi_get_hostap_private_internal();
struct sta_info *sta = ap_get_sta(hapd, bssid);
if (auth_transaction == SAE_MSG_COMMIT) {
if (sta && sta->sae_commit_processing) {
/* Ignore commit msg as we are already processing commit msg for this station */
return ESP_OK;
}
return auth_sae_queue(hapd, buf, len, bssid, status, auth_transaction);
}
if (sta && auth_transaction == SAE_MSG_CONFIRM) {
struct sae_hostap_confirm_data *frm = os_malloc(sizeof(struct sae_hostap_confirm_data) + len);
if (!frm) {
wpa_printf(MSG_ERROR, "failed to allocate memory for confirm event");
return ESP_FAIL;
}
frm->len = len;
os_memcpy(frm->bssid, bssid, ETH_ALEN);
frm->auth_transaction = auth_transaction;
frm->status = status;
os_memcpy(frm->msg, buf, len);
if (wpa3_hostap_post_evt(SIG_WPA3_RX_CONFIRM, (u32)frm) != 0) {
wpa_printf(MSG_ERROR, "failed to queue confirm build event");
os_free(frm);
return ESP_FAIL;
}
}
return ESP_OK;
}
int esp_send_sae_auth_reply(struct hostapd_data *hapd,
const u8 *dst, const u8 *bssid,
u16 auth_alg, u16 auth_transaction, u16 resp,
const u8 *ies, size_t ies_len)
{
int reply_res = ESP_FAIL;
ies_len += 3 * sizeof(uint16_t);
wifi_mgmt_frm_req_t *req = os_zalloc(sizeof(*req) + ies_len);
if (!req) {
wpa_printf(MSG_ERROR, "failed to send sae auth reply");
return reply_res;
}
((uint16_t *)req->data)[0] = htole16(auth_alg);
((uint16_t *)req->data)[1] = htole16(auth_transaction);
((uint16_t *)req->data)[2] = htole16(resp);
os_memcpy(&((uint16_t *)req->data)[3], ies, ies_len - 3 * sizeof(uint16_t));
req->ifx = WIFI_IF_AP;
req->subtype = WLAN_FC_STYPE_AUTH;
req->data_len = ies_len;
os_memcpy(req->da, bssid, ETH_ALEN);
if (esp_wifi_send_mgmt_frm_internal(req) != 0) {
wpa_printf(MSG_INFO, "%s: send failed", __func__);
} else {
reply_res = ESP_OK;
}
os_free(req);
return reply_res;
}
void esp_wifi_register_wpa3_ap_cb(struct wpa_funcs *wpa_cb)
{
wpa_cb->wpa3_hostap_handle_auth = wpa3_hostap_handle_auth;
}
#endif /* CONFIG_SAE */