本文将结合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_commit
和sme_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
)已经填充了以下内容:
- Group ID
- Scalar
- 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
中:
sme_auth_build_sae_commit
作为参数buf
传入了sme_external_auth_build_buf
, 首先对它的size进行重新分配, 确保能放得下authentication 帧- 在buffer中填入802.11 MAC header
- 在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 的帧, 这个函数的返回值如下:
- < 0: 来自AP 的帧不符合802.11协议的要求, 所以认证失败
- 0: 正常处理来自AP 的commit帧并发送confirm 帧
- 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 帧的处理. 这里面有三个重要的函数
sae_parse_commit
解析commit帧, 验证scalar和 FFE(commit-element) 是否符合802.11 的规范sae_process_commit
计算PMKsme_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_confirm
和sme_external_auth_send_sae_commit
十分类似. 先后调用了sme_auth_build_sae_confirm
和sme_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;
}