wpa_supplicant2.9之sae握手流程
WPA3-Personal采用了新的加密方式,SAE算法。
WPA2的wifi连接流程:auth只有两帧
STA ------------> Authentication Request --------> AP
STA <------------ Authentication Response <------ AP
STA -------------> Association Request ------------> AP
STA <------------- Association Response <----------- AP
STA <------------- EAPOL-KEY <----------- AP
STA ------------> EAPOL-KEY --------> AP
STA <------------- EAPOL-KEY <----------- AP
STA ------------> EAPOL-KEY --------> AP
而WPA3-SAE的连接过程如下:auth有四帧(图盗的网上的) 由上可知SAE的Authentication流程有四帧。 本文及续篇主要是分析wpa_supplicant中STA设备连接时SAE Authentication流程相关的源码。
常见的sae auth方式有两种,一种是支持external auth的方式,另一种是支持SME的方式。 本篇讲SME的流程,下一篇讲external auth的流程。
SME介绍
先解释下SME(station management entity 站点管理实体)。为了方便对802.11MAC和PHY层的统一管理,定义了SME。该实体独立于MAC和PHY层,使用者可通过SME实体来操作MAC和PHY层的SAP。 关于SME的更详细的解释可以看这篇:https://blog.csdn.net/qq_33307581/article/details/110151985
wpa_supplicant_connect
在wpa_supplicant中,wpa_supplicant_connect开启连接流程。
wpa_supplicant_connect------>wpa_supplicant_associate
wpa_supplicant_associate函数里正式进入authentication流程(wpa_supplicant_associate这个函数名仅仅是个名称,不代表此函数中执行的是associate流程 )。
wpa_supplicant_associate函数中,会通过驱动提供的符号位参数来判断下是否使用SME,若使用SME,则进入由sme_authenticate的流程。本部分代码如下:
if ((wpa_s->drv_flags & WPA_DRIVER_FLAGS_SME) &&
ssid->mode == WPAS_MODE_INFRA) {
sme_authenticate(wpa_s, bss, ssid);
return;
}
其中,wpa_s->drv_flags是在函数wpa_supplicant_init_iface中初始化接口时注册的。 wpa_supplicant_init_iface中注册语句:
capa_res = wpa_drv_get_capa(wpa_s, &capa);
if (capa_res == 0) {
... ...
wpa_s->drv_flags = capa.flags;
... ...
}
接下来开始正题了,开始分析从sme_authenticate为起点的支持SME的SAE AUTH流程。
sme_authenticate
代码如下:
void sme_authenticate(struct wpa_supplicant *wpa_s,
struct wpa_bss *bss, struct wpa_ssid *ssid)
{
struct wpa_connect_work *cwork;
if (bss == NULL || ssid == NULL)
return;
if (wpa_s->connect_work) {
wpa_dbg(wpa_s, MSG_DEBUG, "SME: Reject sme_authenticate() call since connect_work exist");
return;
}
if (radio_work_pending(wpa_s, "sme-connect")) {
wpa_dbg(wpa_s, MSG_DEBUG,
"SME: Remove previous pending sme-connect");
radio_remove_works(wpa_s, "sme-connect", 0);
}
wpas_abort_ongoing_scan(wpa_s);
cwork = os_zalloc(sizeof(*cwork));
if (cwork == NULL)
return;
cwork->bss = bss;
cwork->ssid = ssid;
cwork->sme = 1;
#ifdef CONFIG_SAE
wpa_s->sme.sae.state = SAE_NOTHING;
wpa_s->sme.sae.send_confirm = 0;
wpa_s->sme.sae_group_index = 0;
#endif
if (radio_add_work(wpa_s, bss->freq, "sme-connect", 1,
sme_auth_start_cb, cwork) < 0)
wpas_connect_work_free(cwork);
}
关于radio_work: 在wpa_s中包含着一个重要的链表wpa_s->radio,该链表中包含着wpa_s指定驱动进行的一系列射频操作,例如scan,associate,authentication等。由于驱动的射频类操作能一项一项的执行,且耗时很长,所以只能先将待操作的事件存储到链表中,等待驱动的顺序执行。执行完当前的radio_work后,会将下一个radio work添加到timeout 链表中。
sme_auth_start_cb
static void sme_auth_start_cb(struct wpa_radio_work *work, int deinit)
{
struct wpa_connect_work *cwork = work->ctx;
struct wpa_supplicant *wpa_s = work->wpa_s;
if (deinit) {
if (work->started)
wpa_s->connect_work = NULL;
wpas_connect_work_free(cwork);
return;
}
wpa_s->connect_work = work;
if (cwork->bss_removed ||
!wpas_valid_bss_ssid(wpa_s, cwork->bss, cwork->ssid) ||
wpas_network_disabled(wpa_s, cwork->ssid)) {
wpa_dbg(wpa_s, MSG_DEBUG, "SME: BSS/SSID entry for authentication not valid anymore - drop connection attempt");
wpas_connect_work_done(wpa_s);
return;
}
wpa_sm_set_assoc_wpa_ie(wpa_s->wpa, NULL, 0);
sme_send_authentication(wpa_s, cwork->bss, cwork->ssid, 1);
}
sme_auth_start_cb最终会调sme_send_authentication。
sme_send_authentication
static void sme_send_authentication(struct wpa_supplicant *wpa_s,
struct wpa_bss *bss, struct wpa_ssid *ssid,
int start)
{
struct wpa_driver_auth_params params;
struct wpa_ssid *old_ssid;
... ... ... ...
if (bss == NULL) {
wpa_msg(wpa_s, MSG_ERROR, "SME: No scan result available for "
"the network");
wpas_connect_work_done(wpa_s);
return;
}
skip_auth = wpa_s->conf->reassoc_same_bss_optim &&
wpa_s->reassoc_same_bss;
wpa_s->current_bss = bss;
os_memset(¶ms, 0, sizeof(params));
wpa_s->reassociate = 0;
params.freq = bss->freq;
... ... ... ...
#ifdef CONFIG_SAE
if (!skip_auth && params.auth_alg == WPA_AUTH_ALG_SAE &&
pmksa_cache_set_current(wpa_s->wpa, NULL, bss->bssid, ssid, 0,
NULL,
wpa_s->key_mgmt == WPA_KEY_MGMT_FT_SAE ?
WPA_KEY_MGMT_FT_SAE :
WPA_KEY_MGMT_SAE) == 0) {
wpa_dbg(wpa_s, MSG_DEBUG,
"PMKSA cache entry found - try to use PMKSA caching instead of new SAE authentication");
wpa_sm_set_pmk_from_pmksa(wpa_s->wpa);
params.auth_alg = WPA_AUTH_ALG_OPEN;
wpa_s->sme.sae_pmksa_caching = 1;
}
if (!skip_auth && params.auth_alg == WPA_AUTH_ALG_SAE) {
if (start)
resp = sme_auth_build_sae_commit(wpa_s, ssid,
bss->bssid, 0,
start == 2);
else
resp = sme_auth_build_sae_confirm(wpa_s, 0);
if (resp == NULL) {
wpas_connection_failed(wpa_s, bss->bssid);
return;
}
params.auth_data = wpabuf_head(resp);
params.auth_data_len = wpabuf_len(resp);
wpa_s->sme.sae.state = start ? SAE_COMMITTED : SAE_CONFIRMED;
}
#endif
... ... ... ...
wpa_supplicant_cancel_sched_scan(wpa_s);
wpa_supplicant_cancel_scan(wpa_s);
wpa_msg(wpa_s, MSG_INFO, "SME: Trying to authenticate with " MACSTR
" (SSID='%s' freq=%d MHz)", MAC2STR(params.bssid),
wpa_ssid_txt(params.ssid, params.ssid_len), params.freq);
eapol_sm_notify_portValid(wpa_s->eapol, FALSE);
wpa_clear_keys(wpa_s, bss->bssid);
wpa_supplicant_set_state(wpa_s, WPA_AUTHENTICATING);
if (old_ssid != wpa_s->current_ssid)
wpas_notify_network_changed(wpa_s);
... ... ... ...
if (skip_auth) {
wpa_msg(wpa_s, MSG_DEBUG,
"SME: Skip authentication step on reassoc-to-same-BSS");
wpabuf_free(resp);
sme_associate(wpa_s, ssid->mode, bss->bssid, WLAN_AUTH_OPEN);
return;
}
wpa_s->sme.auth_alg = params.auth_alg;
if (wpa_drv_authenticate(wpa_s, ¶ms) < 0) {
wpa_msg(wpa_s, MSG_INFO, "SME: Authentication request to the "
"driver failed");
wpas_connection_failed(wpa_s, bss->bssid);
wpa_supplicant_mark_disassoc(wpa_s);
wpabuf_free(resp);
wpas_connect_work_done(wpa_s);
return;
}
eloop_register_timeout(SME_AUTH_TIMEOUT, 0, sme_auth_timer, wpa_s,
NULL);
wpabuf_free(resp);
}
wpa_drv_authenticate
static inline int wpa_drv_authenticate(struct wpa_supplicant *wpa_s,
struct wpa_driver_auth_params *params)
{
if (wpa_s->driver->authenticate)
return wpa_s->driver->authenticate(wpa_s->drv_priv, params);
return -1;
}
authenticate函数的注册是在wpa_driver_nl80211_ops 里。
const struct wpa_driver_ops wpa_driver_nl80211_ops = {
.name = "nl80211",
.desc = "Linux nl80211/cfg80211",
... ... ... ...
.authenticate = driver_nl80211_authenticate,
.associate = wpa_driver_nl80211_associate,
... ... ... ...
};
故,实际调用的driver_nl80211_authenticate。 driver_nl80211_authenticate代码如下:
static int driver_nl80211_authenticate(void *priv,
struct wpa_driver_auth_params *params)
{
struct i802_bss *bss = priv;
return wpa_driver_nl80211_authenticate(bss, params);
}
wpa_driver_nl80211_authenticate
static int wpa_driver_nl80211_authenticate(
struct i802_bss *bss, struct wpa_driver_auth_params *params)
{
struct wpa_driver_nl80211_data *drv = bss->drv;
int ret = -1, i;
struct nl_msg *msg;
enum nl80211_auth_type type;
... ... ... ...
retry:
wpa_printf(MSG_DEBUG, "nl80211: Authenticate (ifindex=%d)",
drv->ifindex);
msg = nl80211_drv_msg(drv, 0, NL80211_CMD_AUTHENTICATE);
if (!msg)
goto fail;
... ... ... ...
ret = send_and_recv_msgs(drv, msg, NULL, NULL);
msg = NULL;
if (ret) {
wpa_dbg(drv->ctx, MSG_DEBUG,
"nl80211: MLME command failed (auth): count=%d ret=%d (%s)",
count, ret, strerror(-ret));
count++;
... ...
} else
wpa_printf(MSG_DEBUG,
"nl80211: Authentication request send successfully");
fail:
nlmsg_free(msg);
return ret;
}
NL80211_CMD_XXX指令发给驱动,驱动会创建一个同名指令作回调。处理回调指令的函数就是do_process_drv_event。
do_process_drv_event
static void do_process_drv_event(struct i802_bss *bss, int cmd,
struct nlattr **tb)
{
struct wpa_driver_nl80211_data *drv = bss->drv;
int external_scan_event = 0;
wpa_printf(MSG_DEBUG, "nl80211: Drv Event %d (%s) received for %s",
cmd, nl80211_command_to_string(cmd), bss->ifname);
... ... ... ...
switch (cmd) {
case XXX:
... ... ... ...
case NL80211_CMD_AUTHENTICATE:
case NL80211_CMD_ASSOCIATE:
case NL80211_CMD_DEAUTHENTICATE:
case NL80211_CMD_DISASSOCIATE:
case NL80211_CMD_FRAME_TX_STATUS:
case NL80211_CMD_UNPROT_DEAUTHENTICATE:
case NL80211_CMD_UNPROT_DISASSOCIATE:
mlme_event(bss, cmd, tb[NL80211_ATTR_FRAME],
tb[NL80211_ATTR_MAC], tb[NL80211_ATTR_TIMED_OUT],
tb[NL80211_ATTR_WIPHY_FREQ], tb[NL80211_ATTR_ACK],
tb[NL80211_ATTR_COOKIE],
tb[NL80211_ATTR_RX_SIGNAL_DBM],
tb[NL80211_ATTR_STA_WME],
tb[NL80211_ATTR_REQ_IE]);
break;
case NL80211_CMD_CONNECT:
case NL80211_CMD_ROAM:
mlme_event_connect(drv, cmd,
tb[NL80211_ATTR_STATUS_CODE],
tb[NL80211_ATTR_MAC],
tb[NL80211_ATTR_REQ_IE],
tb[NL80211_ATTR_RESP_IE],
tb[NL80211_ATTR_TIMED_OUT],
tb[NL80211_ATTR_TIMEOUT_REASON],
NULL, NULL, NULL,
tb[NL80211_ATTR_FILS_KEK],
NULL,
tb[NL80211_ATTR_FILS_ERP_NEXT_SEQ_NUM],
tb[NL80211_ATTR_PMK],
tb[NL80211_ATTR_PMKID]);
break;
case XXX:
... ... ... ...
default:
wpa_dbg(drv->ctx, MSG_DEBUG, "nl80211: Ignored unknown event "
"(cmd=%d)", cmd);
break;
}
}
会接着调用mlme_event(bss, NL80211_CMD_AUTHENTICATION, … …)。 mlme_event中,case NL80211_CMD_AUTHENTICATE会调用mlme_event_auth处理。
mlme_event_auth
static void mlme_event_auth(struct wpa_driver_nl80211_data *drv,
const u8 *frame, size_t len)
{
const struct ieee80211_mgmt *mgmt;
union wpa_event_data event;
if (!(drv->capa.flags & WPA_DRIVER_FLAGS_SME) &&
drv->force_connect_cmd) {
wpa_printf(MSG_DEBUG,
"nl80211: Ignore auth event when using driver SME");
return;
}
wpa_printf(MSG_DEBUG, "nl80211: Authenticate event");
mgmt = (const struct ieee80211_mgmt *) frame;
if (len < 24 + sizeof(mgmt->u.auth)) {
wpa_printf(MSG_DEBUG, "nl80211: Too short association event "
"frame");
return;
}
... ... ... ...
wpa_supplicant_event(drv->ctx, EVENT_AUTH, &event);
}
wpa_supplicant_event中,对于EVENT_AUTH会调用sme_event_auth函数
sme_event_auth
void sme_event_auth(struct wpa_supplicant *wpa_s, union wpa_event_data *data)
{
struct wpa_ssid *ssid = wpa_s->current_ssid;
... ... ... ...
wpa_dbg(wpa_s, MSG_DEBUG, "SME: Authentication response: peer=" MACSTR
" auth_type=%d auth_transaction=%d status_code=%d", MAC2STR(data->auth.peer), data->auth.auth_type, data->auth.auth_transaction, data->auth.status_code);
wpa_hexdump(MSG_MSGDUMP, "SME: Authentication response IEs", data->auth.ies, data->auth.ies_len);
eloop_cancel_timeout(sme_auth_timer, wpa_s, NULL);
#ifdef CONFIG_SAE
if (data->auth.auth_type == WLAN_AUTH_SAE) {
int res;
res = sme_sae_auth(wpa_s, data->auth.auth_transaction, data->auth.status_code, data->auth.ies, data->auth.ies_len, 0, NULL);
if (res < 0) {
wpas_connection_failed(wpa_s, wpa_s->pending_bssid);
wpa_supplicant_set_state(wpa_s, WPA_DISCONNECTED);
}
if (res != 1)
return;
wpa_printf(MSG_DEBUG, "SME: SAE completed - setting PMK for "
"4-way handshake");
wpa_sm_set_pmk(wpa_s->wpa, wpa_s->sme.sae.pmk, PMK_LEN, wpa_s->sme.sae.pmkid, wpa_s->pending_bssid);
}
#endif
if (data->auth.status_code != WLAN_STATUS_SUCCESS) {
... ... ... ...
}
sme_associate(wpa_s, ssid->mode, data->auth.peer,
data->auth.auth_type);
}
sme_sae_auth
static int sme_sae_auth(struct wpa_supplicant *wpa_s, u16 auth_transaction,
u16 status_code, const u8 *data, size_t len,
int external, const u8 *sa)
{
int *groups;
wpa_dbg(wpa_s, MSG_DEBUG, "SME: SAE authentication transaction %u "
"status code %u", auth_transaction, status_code);
... ... ... ...
if (auth_transaction == 1) {
u16 res;
groups = wpa_s->conf->sae_groups;
wpa_dbg(wpa_s, MSG_DEBUG, "SME SAE commit");
... ... ... ...
wpabuf_free(wpa_s->sme.sae_token);
wpa_s->sme.sae_token = NULL;
if (!external)
sme_send_authentication(wpa_s, wpa_s->current_bss,
wpa_s->current_ssid, 0);
else
sme_external_auth_send_sae_confirm(wpa_s, sa);
return 0;
} else if (auth_transaction == 2) {
wpa_dbg(wpa_s, MSG_DEBUG, "SME SAE confirm");
if (wpa_s->sme.sae.state != SAE_CONFIRMED)
return -1;
if (sae_check_confirm(&wpa_s->sme.sae, data, len) < 0)
return -1;
wpa_s->sme.sae.state = SAE_ACCEPTED;
sae_clear_temp_data(&wpa_s->sme.sae);
if (external) {
sme_send_external_auth_status(wpa_s, WLAN_STATUS_SUCCESS);
}
return 1;
}
return -1;
}
confirm阶段又回到sme_send_authentication函数,这一次会在函数内调用sme_auth_build_sae_confirm生成sae confirm,再调用一次wpa_drv_authenticate重复整个流程。 当wpa_supplicant_event中再次收到EVENT_AUTH,仍调用sme_event_auth函数。 在sme_event_auth函数里,若confirm阶段成功,则调用sme_associate进入associate阶段。
至此,WPASv2.9的SAE之支持SME的auth四次握手阶段结束。
|