SAE authentication STA端代码实现

本文将结合wpa_supplicant代码查看一下SAE authentication的实现。主要会关注SAE认证过程中重要变量的生成以及对于对端发送的帧的处理过程。

关于SAE 认证的原理请参考 SAE(WPA3-Personal)认证原理简介

SAE重要的数据结构

struct sae_data {
    // SAE状态机的四种状态 SAE_NOTHING, SAE_COMMITTED, SAE_CONFIRMED, SAE_ACCEPTED
	enum sae_state state;
	// Confirm 帧中的send-confim 计数器
	u16 send_confirm;
	// PMK
	u8 pmk[SAE_PMK_LEN_MAX];
	size_t pmk_len;
	// AKM, 决定用什么PRF函数生成PTK
	int akmp; /* WPA_KEY_MGMT_* used in key derivation */
	u32 own_akm_suite_selector;
	u32 peer_akm_suite_selector;
	u8 pmkid[SAE_PMKID_LEN];
	struct crypto_bignum *peer_commit_scalar;
	struct crypto_bignum *peer_commit_scalar_accepted;
	// ECC group
	int group;
	unsigned int sync; /* protocol instance variable: Sync */
	u16 rc; /* protocol instance variable: Rc (received send-confirm) */
	unsigned int h2e:1;
	unsigned int pk:1;
	unsigned int no_pw_id:1;
	// SAE 过程中的各种临时数据
	struct sae_temporary_data *tmp;
};
struct sae_pt {
	struct sae_pt *next;
	// ECC group ID
	int group;
	// 存储椭圆曲线的参数
	struct crypto_ec *ec;
	struct crypto_ec_point *ecc_pt;

    // FFC中使用的变量
	const struct dh_group *dh;
	struct crypto_bignum *ffc_pt;
#ifdef CONFIG_SAE_PK
	u8 ssid[32];
	size_t ssid_len;
#endif /* CONFIG_SAE_PK */
};

PT 计算

在 STA这边, 会在发起连接之前计算 P T \bm{PT} PT 的值, 具体来说, 是在wpa_supplicant_associate中.

void wpa_supplicant_associate(struct wpa_supplicant *wpa_s,
   		      struct wpa_bss *bss, struct wpa_ssid *ssid)
{
...
#ifdef CONFIG_SAE
   wpa_s_setup_sae_pt(wpa_s->conf, ssid, false);
#endif /* CONFIG_SAE */
...
   	return;
}
void wpa_s_setup_sae_pt(struct wpa_config *conf, struct wpa_ssid *ssid,
			bool force)
{
#ifdef CONFIG_SAE
    // conf->sae_groups 一般不会特地配置, 所以值为0
	int *groups = conf->sae_groups;
	// 默认会计算ECC group 19, 20, 21的PT, 0不是group ID, 而是用来标识数组边界
	int default_groups[] = { 19, 20, 21, 0 };
	const char *password;

	if (!groups || groups[0] <= 0)
		groups = default_groups;

	password = ssid->sae_password;
	if (!password)
		password = ssid->passphrase;
    // 如果要连接的AP是采用的SAE的认证方式的话
    // 1. 若AP只支持SAE_PWE_HUNT_AND_PECK则不计算PT
    // 2. 若AP支持H2E, 则计算PT
	if (!password ||
	    !wpa_key_mgmt_sae(ssid->key_mgmt) ||
	    (conf->sae_pwe == SAE_PWE_HUNT_AND_PECK && !ssid->sae_password_id &&
	     !wpa_key_mgmt_sae_ext_key(ssid->key_mgmt) &&
	     !force &&
	     !sae_pk_valid_password(password)) ||
	    conf->sae_pwe == SAE_PWE_FORCE_HUNT_AND_PECK) {
		/* PT derivation not needed */
		sae_deinit_pt(ssid->pt);
		ssid->pt = NULL;
		return;
	}

	if (ssid->pt)
		return; /* PT already derived */
	// 计算每个group的PT存放在ssid->pt当中
	ssid->pt = sae_derive_pt(groups, ssid->ssid, ssid->ssid_len,
				 (const u8 *) password, os_strlen(password),
				 ssid->sae_password_id);
#endif /* CONFIG_SAE */
}
struct sae_pt * sae_derive_pt(int *groups, const u8 *ssid, size_t ssid_len,
			      const u8 *password, size_t password_len,
			      const char *identifier)
{
	struct sae_pt *pt = NULL, *last = NULL, *tmp;
	int default_groups[] = { 19, 0 };
	int i;

	if (!groups)
		groups = default_groups;
	for (i = 0; groups[i] > 0; i++) {
	    // 对每一个分组都计算相应的sae_pt 然后附在链表最后
		tmp = sae_derive_pt_group(groups[i], ssid, ssid_len, password,
					  password_len, identifier);
		if (!tmp)
			continue;

		if (last)
			last->next = tmp;
		else
			pt = tmp;
		last = tmp;
	}

	return pt;
}

STA 生成commit帧

在这里插入图片描述
STA的commit帧在sme_external_auth_send_sae_commit中进行填充并发送.

简单来说, 通过sme_auth_build_sae_commitsme_external_auth_build_buf填充commit 帧, 然后调用wpa_drv_send_mlme将commit 帧发送出去.

static int sme_external_auth_send_sae_commit(struct wpa_supplicant *wpa_s,
					     const u8 *bssid,
					     struct wpa_ssid *ssid)
{
	struct wpabuf *resp, *buf;
	int use_pt;
	bool use_pk;
	u16 status;

	resp = sme_auth_build_sae_commit(wpa_s, ssid, bssid,
					 wpa_s->sme.ext_ml_auth ?
					 wpa_s->sme.ext_auth_ap_mld_addr : NULL,
					 1, 0, &use_pt, &use_pk);
...
	if (use_pk)
		status = WLAN_STATUS_SAE_PK;
	else if (use_pt)
		status = WLAN_STATUS_SAE_HASH_TO_ELEMENT;
	else
		status = WLAN_STATUS_SUCCESS;
	sme_external_auth_build_buf(buf, resp, wpa_s->own_addr,
				    wpa_s->sme.ext_ml_auth ?
				    wpa_s->sme.ext_auth_ap_mld_addr : bssid, 1,
				    wpa_s->sme.seq_num, status,
				    wpa_s->sme.ext_ml_auth ?
				    wpa_s->own_addr : NULL);


	return 0;
}

sme_auth_build_sae_commit做了两件事, 通过sae_prepare_commit_pt(或sae_prepare_commit)计算Scalar 和 FFE, 然后将它保存在wpa_s->sme.sae

然后调用sae_write_commit将Scalar 和 FFE 写入 buffer, 这时的buffer(sme_external_auth_send_sae_commit中的resp)已经填充了以下内容:

  1. Group ID
  2. Scalar
  3. commit-element
static struct wpabuf * sme_auth_build_sae_commit(struct wpa_supplicant *wpa_s,
						 struct wpa_ssid *ssid,
						 const u8 *bssid,
						 const u8 *mld_addr,
						 int external,
						 int reuse, int *ret_use_pt,
						 bool *ret_use_pk)
{
...
	if (use_pt &&
	    sae_prepare_commit_pt(&wpa_s->sme.sae, ssid->pt,
				  wpa_s->own_addr, addr,
				  wpa_s->sme.sae_rejected_groups, NULL) < 0)
		goto fail;
	if (!use_pt &&
	    sae_prepare_commit(wpa_s->own_addr, addr,
			       (u8 *) password, os_strlen(password),
			       &wpa_s->sme.sae) < 0) {
		wpa_printf(MSG_DEBUG, "SAE: Could not pick PWE");
		goto fail;
	}
...
	if (sae_write_commit(&wpa_s->sme.sae, buf, wpa_s->sme.sae_token,
			     ssid->sae_password_id) < 0) {
		wpabuf_free(buf);
		goto fail;
	}
...

	str_clear_free(password);
	return buf;

fail:
	str_clear_free(password);
	return NULL;
}

sme_external_auth_build_buf 中:

  1. sme_auth_build_sae_commit作为参数buf传入了sme_external_auth_build_buf, 首先对它的size进行重新分配, 确保能放得下authentication 帧
  2. 在buffer中填入802.11 MAC header
  3. 在buffer中填入auth_alg, seq_ctrl, auth_transaction, status_code这些authentication帧的payload
static int sme_external_auth_build_buf(struct wpabuf *buf,
				       struct wpabuf *params,
				       const u8 *sa, const u8 *da,
				       u16 auth_transaction, u16 seq_num,
				       u16 status_code, const u8 *mld_addr)
{
	struct ieee80211_mgmt *resp;

	resp = wpabuf_put(buf, offsetof(struct ieee80211_mgmt,
					u.auth.variable));

	resp->frame_control = host_to_le16((WLAN_FC_TYPE_MGMT << 2) |
					   (WLAN_FC_STYPE_AUTH << 4));
	os_memcpy(resp->da, da, ETH_ALEN);
	os_memcpy(resp->sa, sa, ETH_ALEN);
	os_memcpy(resp->bssid, da, ETH_ALEN);
	resp->u.auth.auth_alg = host_to_le16(WLAN_AUTH_SAE);
	resp->seq_ctrl = host_to_le16(seq_num << 4);
	resp->u.auth.auth_transaction = host_to_le16(auth_transaction);
	resp->u.auth.status_code = host_to_le16(status_code);
	if (params)
		wpabuf_put_buf(buf, params);

	if (mld_addr)
		wpa_auth_ml_ie(buf, mld_addr);

	return 0;
}
// ieee80211_mgmt的结构如下.
struct ieee80211_mgmt {
	le16 frame_control;
	le16 duration;
	u8 da[6];
	u8 sa[6];
	u8 bssid[6];
	le16 seq_ctrl;
	union {
		struct {
			le16 auth_alg;
			le16 auth_transaction;
			le16 status_code;
			/* possibly followed by Challenge text */
			u8 variable[];
		} STRUCT_PACKED auth;

STA 处理来自AP 的帧

来自AP 的帧, 无论是commit 还是 confirm 都是在sme_external_auth_mgmt_rx 中进行处理. 它调用sme_sae_auth 处理来自AP 的帧, 这个函数的返回值如下:

  1. < 0: 来自AP 的帧不符合802.11协议的要求, 所以认证失败
  2. 0: 正常处理来自AP 的commit帧并发送confirm 帧
  3. 1: SAE 认证成功
void sme_external_auth_mgmt_rx(struct wpa_supplicant *wpa_s,
			       const u8 *auth_frame, size_t len)
{
	const struct ieee80211_mgmt *header;
	size_t auth_length;

	header = (const struct ieee80211_mgmt *) auth_frame;
	auth_length = IEEE80211_HDRLEN + sizeof(header->u.auth);

	if (len < auth_length) {
		/* Notify failure to the driver */
		sme_send_external_auth_status(wpa_s,
					      WLAN_STATUS_UNSPECIFIED_FAILURE);
		return;
	}

	if (le_to_host16(header->u.auth.auth_alg) == WLAN_AUTH_SAE) {
		int res;
		int ie_offset = 0;

		res = sme_sae_auth(
			wpa_s, le_to_host16(header->u.auth.auth_transaction),
			le_to_host16(header->u.auth.status_code),
			header->u.auth.variable,
			len - auth_length, 1, header->sa, &ie_offset);
	    // res < 0, SAE认证失败
		if (res < 0) {
			/* Notify failure to the driver */
			sme_send_external_auth_status(
				wpa_s,
				res == -2 ?
				le_to_host16(header->u.auth.status_code) :
				WLAN_STATUS_UNSPECIFIED_FAILURE);
			return;
		}
		// res = 1, 正常处理来自AP 的commit 帧
		if (res != 1)
			return;

		// res = 1, SAE 认证成功, 设置PMK
		if (sme_sae_set_pmk(wpa_s,
				    wpa_s->sme.ext_ml_auth ?
				    wpa_s->sme.ext_auth_ap_mld_addr :
				    wpa_s->sme.ext_auth_bssid) < 0)
			return;
	}
}

Commit 帧

先来看sme_sae_auth 对于commit 帧的处理. 这里面有三个重要的函数

  1. sae_parse_commit 解析commit帧, 验证scalar和 FFE(commit-element) 是否符合802.11 的规范
  2. sae_process_commit 计算PMK
  3. sme_external_auth_send_sae_confirm 发送confirm 帧
	if (auth_transaction == 1) {
		u16 res;

		groups = wpa_s->conf->sae_groups;

		wpa_dbg(wpa_s, MSG_DEBUG, "SME SAE commit");
...
		if (wpa_s->sme.sae.state != SAE_COMMITTED) {
			wpa_printf(MSG_DEBUG,
				   "SAE: Ignore commit message while waiting for confirm");
			return 0;
		}
        // 如果采用 H2E, commit中的status code应该是 WLAN_STATUS_SAE_HASH_TO_ELEMENT
		if (wpa_s->sme.sae.h2e && status_code == WLAN_STATUS_SUCCESS) {
			wpa_printf(MSG_DEBUG,
				   "SAE: Unexpected use of status code 0 in SAE commit when H2E was expected");
			return -1;
		}
		// 只有采用H2E的时候, status code才能是 WLAN_STATUS_SAE_HASH_TO_ELEMENT
		if ((!wpa_s->sme.sae.h2e || wpa_s->sme.sae.pk) &&
		    status_code == WLAN_STATUS_SAE_HASH_TO_ELEMENT) {
			wpa_printf(MSG_DEBUG,
				   "SAE: Unexpected use of status code for H2E in SAE commit when H2E was not expected");
			return -1;
		}
		// SAE PK相关实现
		if (!wpa_s->sme.sae.pk &&
		    status_code == WLAN_STATUS_SAE_PK) {
			wpa_printf(MSG_DEBUG,
				   "SAE: Unexpected use of status code for PK in SAE commit when PK was not expected");
			return -1;
		}

		if (groups && groups[0] <= 0)
			groups = NULL;
		res = sae_parse_commit(&wpa_s->sme.sae, data, len, NULL, NULL,
				       groups, status_code ==
				       WLAN_STATUS_SAE_HASH_TO_ELEMENT ||
				       status_code == WLAN_STATUS_SAE_PK,
				       ie_offset);
		if (res == SAE_SILENTLY_DISCARD) {
			wpa_printf(MSG_DEBUG,
				   "SAE: Drop commit message due to reflection attack");
			return 0;
		}
		if (res != WLAN_STATUS_SUCCESS)
			return -1;
...

		if (sae_process_commit(&wpa_s->sme.sae) < 0) {
			wpa_printf(MSG_DEBUG, "SAE: Failed to process peer "
				   "commit");
			return -1;
		}

		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 {
			if (wpa_s->sme.ext_ml_auth &&
			    sme_external_ml_auth(wpa_s, data, len, *ie_offset,
						 status_code))
				return -1;
            // 在这发送 confirm帧
			sme_external_auth_send_sae_confirm(wpa_s, sa);
		}
		return 0;

sae_parse_commit

先来看sae_parse_commit 的实现, 它检查了来自AP 的scalar和 FFE(commit-element) 是否符合802.11 的规范, 具体可以查看 SAE(WPA3-Personal)认证原理简介

u16 sae_parse_commit(struct sae_data *sae, const u8 *data, size_t len,
		     const u8 **token, size_t *token_len, int *allowed_groups,
		     int h2e, int *ie_offset)
{
	const u8 *pos = data, *end = data + len;
	u16 res;

...

	/* commit-scalar */
	res = sae_parse_commit_scalar(sae, &pos, end);
	if (res != WLAN_STATUS_SUCCESS)
		return res;

	/* commit-element */
	res = sae_parse_commit_element(sae, &pos, end);
	if (res != WLAN_STATUS_SUCCESS)
		return res;
...

	/*
	 * Check whether peer-commit-scalar and PEER-COMMIT-ELEMENT are same as
	 * the values we sent which would be evidence of a reflection attack.
	 */
	if (!sae->tmp->own_commit_scalar ||
	    crypto_bignum_cmp(sae->tmp->own_commit_scalar,
			      sae->peer_commit_scalar) != 0 ||
	    (sae->tmp->dh &&
	     (!sae->tmp->own_commit_element_ffc ||
	      crypto_bignum_cmp(sae->tmp->own_commit_element_ffc,
				sae->tmp->peer_commit_element_ffc) != 0)) ||
	    (sae->tmp->ec &&
	     (!sae->tmp->own_commit_element_ecc ||
	      crypto_ec_point_cmp(sae->tmp->ec,
				  sae->tmp->own_commit_element_ecc,
				  sae->tmp->peer_commit_element_ecc) != 0)))
		return WLAN_STATUS_SUCCESS; /* scalars/elements are different */

	/*
	 * This is a reflection attack - return special value to trigger caller
	 * to silently discard the frame instead of replying with a specific
	 * status code.
	 */
	return SAE_SILENTLY_DISCARD;
}

static u16 sae_parse_commit_scalar(struct sae_data *sae, const u8 **pos,
				   const u8 *end)
{
	struct crypto_bignum *peer_scalar;

	if (sae->tmp->prime_len > end - *pos) {
		wpa_printf(MSG_DEBUG, "SAE: Not enough data for scalar");
		return WLAN_STATUS_UNSPECIFIED_FAILURE;
	}

	peer_scalar = crypto_bignum_init_set(*pos, sae->tmp->prime_len);
...

	/* 1 < scalar < r */
	if (crypto_bignum_is_zero(peer_scalar) ||
	    crypto_bignum_is_one(peer_scalar) ||
	    crypto_bignum_cmp(peer_scalar, sae->tmp->order) >= 0) {
		wpa_printf(MSG_DEBUG, "SAE: Invalid peer scalar");
		crypto_bignum_deinit(peer_scalar, 0);
		return WLAN_STATUS_UNSPECIFIED_FAILURE;
	}


	crypto_bignum_deinit(sae->peer_commit_scalar, 0);
	// 保存 peer_scalar
	sae->peer_commit_scalar = peer_scalar;
	wpa_hexdump(MSG_DEBUG, "SAE: Peer commit-scalar",
		    *pos, sae->tmp->prime_len);
	*pos += sae->tmp->prime_len;

	return WLAN_STATUS_SUCCESS;
}

static u16 sae_parse_commit_element(struct sae_data *sae, const u8 **pos,
				    const u8 *end)
{
	if (sae->tmp->dh)
		return sae_parse_commit_element_ffc(sae, pos, end);
	return sae_parse_commit_element_ecc(sae, pos, end);
}

static u16 sae_parse_commit_element_ecc(struct sae_data *sae, const u8 **pos,
					const u8 *end)
{
	u8 prime[SAE_MAX_ECC_PRIME_LEN];

	if (2 * sae->tmp->prime_len > end - *pos) {
		wpa_printf(MSG_DEBUG, "SAE: Not enough data for "
			   "commit-element");
		return WLAN_STATUS_UNSPECIFIED_FAILURE;
	}

	if (crypto_bignum_to_bin(sae->tmp->prime, prime, sizeof(prime),
				 sae->tmp->prime_len) < 0)
		return WLAN_STATUS_UNSPECIFIED_FAILURE;

	/* element x and y coordinates < p */
	if (os_memcmp(*pos, prime, sae->tmp->prime_len) >= 0 ||
	    os_memcmp(*pos + sae->tmp->prime_len, prime,
		      sae->tmp->prime_len) >= 0) {
		wpa_printf(MSG_DEBUG, "SAE: Invalid coordinates in peer "
			   "element");
		return WLAN_STATUS_UNSPECIFIED_FAILURE;
	}
...
	sae->tmp->peer_commit_element_ecc =
		crypto_ec_point_from_bin(sae->tmp->ec, *pos);
...
    // AP 的commit-element应该位于椭圆曲线上.
	if (!crypto_ec_point_is_on_curve(sae->tmp->ec,
					 sae->tmp->peer_commit_element_ecc)) {
		wpa_printf(MSG_DEBUG, "SAE: Peer element is not on curve");
		return WLAN_STATUS_UNSPECIFIED_FAILURE;
	}

	*pos += 2 * sae->tmp->prime_len;

	return WLAN_STATUS_SUCCESS;
}

sae_process_commit

sae_process_commit 中计算PMK, 中间变量 K \bm{K} K 的介绍可以参考 SAE(WPA3-Personal)认证原理简介

sae_derive_k_ecc 中计算 K \bm{K} K 并将其 x x x 坐标保存在 u8 k[SAE_MAX_PRIME_LEN]
sae_derive_keys 中计算PMK, 并将其保存在sae->pmk

int sae_process_commit(struct sae_data *sae)
{
	u8 k[SAE_MAX_PRIME_LEN];
	if (sae->tmp == NULL ||
	    (sae->tmp->ec && sae_derive_k_ecc(sae, k) < 0) ||
	    (sae->tmp->dh && sae_derive_k_ffc(sae, k) < 0) ||
	    sae_derive_keys(sae, k) < 0)
		return -1;
	return 0;
}

sme_external_auth_send_sae_confirm

sme_external_auth_send_sae_confirmsme_external_auth_send_sae_commit十分类似. 先后调用了sme_auth_build_sae_confirmsme_external_auth_build_buf填充confirm帧, 然后调用 wpa_drv_send_mlme 发送confirm帧.

static void sme_external_auth_send_sae_confirm(struct wpa_supplicant *wpa_s,
					       const u8 *da)
{
	struct wpabuf *resp, *buf;

	resp = sme_auth_build_sae_confirm(wpa_s, 1);
	if (!resp) {
		wpa_printf(MSG_DEBUG, "SAE: Confirm message buf alloc failure");
		return;
	}

	wpa_s->sme.sae.state = SAE_CONFIRMED;
	buf = wpabuf_alloc(4 + SAE_CONFIRM_MAX_LEN + wpabuf_len(resp) +
			   (wpa_s->sme.ext_ml_auth ? WPA_AUTH_FRAME_ML_IE_LEN :
			    0));
	if (!buf) {
		wpa_printf(MSG_DEBUG, "SAE: Auth Confirm buf alloc failure");
		wpabuf_free(resp);
		return;
	}
	wpa_s->sme.seq_num++;
	sme_external_auth_build_buf(buf, resp, wpa_s->own_addr,
				    da, 2, wpa_s->sme.seq_num,
				    WLAN_STATUS_SUCCESS,
				    wpa_s->sme.ext_ml_auth ?
				    wpa_s->own_addr : NULL);

	wpa_drv_send_mlme(wpa_s, wpabuf_head(buf), wpabuf_len(buf), 1, 0, 0);
	wpabuf_free(resp);
	wpabuf_free(buf);
}

static struct wpabuf * sme_auth_build_sae_confirm(struct wpa_supplicant *wpa_s,
						  int external)
{
	struct wpabuf *buf;

	buf = wpabuf_alloc(4 + SAE_CONFIRM_MAX_LEN);
	if (buf == NULL)
		return NULL;
...
	sae_write_confirm(&wpa_s->sme.sae, buf);

	return buf;
}

// 填充confirm帧中的 send-confirm 和 confirm
int sae_write_confirm(struct sae_data *sae, struct wpabuf *buf)
{
	const u8 *sc;
	size_t hash_len;
	int res;

	if (sae->tmp == NULL)
		return -1;

	hash_len = sae->tmp->kck_len;

	/* Send-Confirm */
	if (sae->send_confirm < 0xffff)
		sae->send_confirm++;
	sc = wpabuf_put(buf, 0);
	wpabuf_put_le16(buf, sae->send_confirm);

	if (sae->tmp->ec)
		res = sae_cn_confirm_ecc(sae, sc, sae->tmp->own_commit_scalar,
					 sae->tmp->own_commit_element_ecc,
					 sae->peer_commit_scalar,
					 sae->tmp->peer_commit_element_ecc,
					 wpabuf_put(buf, hash_len));
	else
...
	if (res)
		return res;
...

	return 0;
}

sme_external_auth_build_buf 前面介绍过了, 在此不再赘述.

Confirm 帧

对于 Confirm 帧的处理十分简单,主要就是查看confirm帧中status code是否是 success

	} else if (auth_transaction == 2) {
	    // 检查 AP 的confirm帧中status code是否是 success
		if (status_code != WLAN_STATUS_SUCCESS)
			return -1;
		wpa_dbg(wpa_s, MSG_DEBUG, "SME SAE confirm");
		if (wpa_s->sme.sae.state != SAE_CONFIRMED)
			return -1;
		// 因为SAE 是对等的认证方式,这里也要验证AP身份
		if (sae_check_confirm(&wpa_s->sme.sae, data, len,
				      ie_offset) < 0)
			return -1;
		if (external && wpa_s->sme.ext_ml_auth &&
		    sme_external_ml_auth(wpa_s, data, len, *ie_offset,
					 status_code))
			return -1;

		wpa_s->sme.sae.state = SAE_ACCEPTED;
		sae_clear_temp_data(&wpa_s->sme.sae);
		wpa_s_clear_sae_rejected(wpa_s);

		if (external) {
			/* Report success to driver */
			sme_send_external_auth_status(wpa_s,
						      WLAN_STATUS_SUCCESS);
		}

		return 1;
	}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值