wlantest: FILS keys and (Re)Association Request/Response frames

Try to derive PTK when FILS shared key authentication is used without
PFS. The list of available PMKs is interpreted as rMSK for this purpose
and PMK and PTK is derived from that. If the resulting PTK (KEK) can be
used to decrypt the encrypted parts of (Re)Association Request/Response
frames, mark the PTK as derived so that encrypted frames during the
association can be decrypted. In addition, write a decrypted version of
the (Re)Association Request/Response frames into the output file.

Signed-off-by: Jouni Malinen <jouni@qca.qualcomm.com>
This commit is contained in:
Jouni Malinen 2017-09-05 19:01:59 +03:00 committed by Jouni Malinen
parent 6c29d95a90
commit c72df3c67c
2 changed files with 313 additions and 8 deletions

View file

@ -12,6 +12,9 @@
#include "common/defs.h" #include "common/defs.h"
#include "common/ieee802_11_defs.h" #include "common/ieee802_11_defs.h"
#include "common/ieee802_11_common.h" #include "common/ieee802_11_common.h"
#include "common/wpa_common.h"
#include "crypto/aes.h"
#include "crypto/aes_siv.h"
#include "crypto/aes_wrap.h" #include "crypto/aes_wrap.h"
#include "wlantest.h" #include "wlantest.h"
@ -110,6 +113,55 @@ static void rx_mgmt_probe_resp(struct wlantest *wt, const u8 *data, size_t len)
} }
static void process_fils_auth(struct wlantest *wt, struct wlantest_bss *bss,
struct wlantest_sta *sta,
const struct ieee80211_mgmt *mgmt, size_t len)
{
struct ieee802_11_elems elems;
u16 trans;
struct wpa_ie_data data;
if (sta->auth_alg != WLAN_AUTH_FILS_SK ||
len < IEEE80211_HDRLEN + sizeof(mgmt->u.auth))
return;
trans = le_to_host16(mgmt->u.auth.auth_transaction);
if (ieee802_11_parse_elems(mgmt->u.auth.variable,
len - IEEE80211_HDRLEN -
sizeof(mgmt->u.auth), &elems, 0) ==
ParseFailed)
return;
if (trans == 1) {
if (!elems.rsn_ie) {
add_note(wt, MSG_INFO,
"FILS Authentication frame missing RSNE");
return;
}
if (wpa_parse_wpa_ie_rsn(elems.rsn_ie - 2,
elems.rsn_ie_len + 2, &data) < 0) {
add_note(wt, MSG_INFO,
"Invalid RSNE in FILS Authentication frame");
return;
}
sta->key_mgmt = data.key_mgmt;
sta->pairwise_cipher = data.pairwise_cipher;
}
if (!elems.fils_nonce) {
add_note(wt, MSG_INFO,
"FILS Authentication frame missing nonce");
return;
}
if (os_memcmp(mgmt->sa, mgmt->bssid, ETH_ALEN) == 0)
os_memcpy(sta->anonce, elems.fils_nonce, FILS_NONCE_LEN);
else
os_memcpy(sta->snonce, elems.fils_nonce, FILS_NONCE_LEN);
}
static void rx_mgmt_auth(struct wlantest *wt, const u8 *data, size_t len) static void rx_mgmt_auth(struct wlantest *wt, const u8 *data, size_t len)
{ {
const struct ieee80211_mgmt *mgmt; const struct ieee80211_mgmt *mgmt;
@ -135,6 +187,7 @@ static void rx_mgmt_auth(struct wlantest *wt, const u8 *data, size_t len)
} }
alg = le_to_host16(mgmt->u.auth.auth_alg); alg = le_to_host16(mgmt->u.auth.auth_alg);
sta->auth_alg = alg;
trans = le_to_host16(mgmt->u.auth.auth_transaction); trans = le_to_host16(mgmt->u.auth.auth_transaction);
status = le_to_host16(mgmt->u.auth.status_code); status = le_to_host16(mgmt->u.auth.status_code);
@ -155,6 +208,8 @@ static void rx_mgmt_auth(struct wlantest *wt, const u8 *data, size_t len)
sta->counters[WLANTEST_STA_COUNTER_AUTH_RX]++; sta->counters[WLANTEST_STA_COUNTER_AUTH_RX]++;
else else
sta->counters[WLANTEST_STA_COUNTER_AUTH_TX]++; sta->counters[WLANTEST_STA_COUNTER_AUTH_TX]++;
process_fils_auth(wt, bss, sta, mgmt, len);
} }
@ -257,12 +312,137 @@ static void rx_mgmt_deauth(struct wlantest *wt, const u8 *data, size_t len,
} }
static const u8 * get_fils_session(const u8 *ies, size_t ies_len)
{
const u8 *ie, *end;
ie = ies;
end = ((const u8 *) ie) + ies_len;
while (ie + 1 < end) {
if (ie + 2 + ie[1] > end)
break;
if (ie[0] == WLAN_EID_EXTENSION &&
ie[1] >= 1 + FILS_SESSION_LEN &&
ie[2] == WLAN_EID_EXT_FILS_SESSION)
return ie;
ie += 2 + ie[1];
}
return NULL;
}
static int try_rmsk(struct wlantest *wt, struct wlantest_bss *bss,
struct wlantest_sta *sta, struct wlantest_pmk *pmk,
const u8 *frame_start, const u8 *frame_ad,
const u8 *frame_ad_end, const u8 *encr_end)
{
size_t pmk_len = 0;
u8 pmk_buf[PMK_LEN_MAX];
struct wpa_ptk ptk;
u8 ick[FILS_ICK_MAX_LEN];
size_t ick_len;
const u8 *aad[5];
size_t aad_len[5];
u8 buf[2000];
if (fils_rmsk_to_pmk(sta->key_mgmt, pmk->pmk, pmk->pmk_len,
sta->snonce, sta->anonce, NULL, 0,
pmk_buf, &pmk_len) < 0)
return -1;
if (fils_pmk_to_ptk(pmk_buf, pmk_len, sta->addr, bss->bssid,
sta->snonce, sta->anonce, &ptk, ick, &ick_len,
sta->key_mgmt, sta->pairwise_cipher,
NULL, NULL) < 0)
return -1;
/* Check AES-SIV decryption with the derived key */
/* AES-SIV AAD vectors */
/* The STA's MAC address */
aad[0] = sta->addr;
aad_len[0] = ETH_ALEN;
/* The AP's BSSID */
aad[1] = bss->bssid;
aad_len[1] = ETH_ALEN;
/* The STA's nonce */
aad[2] = sta->snonce;
aad_len[2] = FILS_NONCE_LEN;
/* The AP's nonce */
aad[3] = sta->anonce;
aad_len[3] = FILS_NONCE_LEN;
/*
* The (Re)Association Request frame from the Capability Information
* field to the FILS Session element (both inclusive).
*/
aad[4] = frame_ad;
aad_len[4] = frame_ad_end - frame_ad;
if (encr_end - frame_ad_end < AES_BLOCK_SIZE ||
encr_end - frame_ad_end > sizeof(buf))
return -1;
if (aes_siv_decrypt(ptk.kek, ptk.kek_len,
frame_ad_end, encr_end - frame_ad_end,
5, aad, aad_len, buf) < 0) {
wpa_printf(MSG_DEBUG,
"FILS: Derived PTK did not match AES-SIV data");
return -1;
}
add_note(wt, MSG_DEBUG, "Derived FILS PTK");
os_memcpy(&sta->ptk, &ptk, sizeof(ptk));
sta->ptk_set = 1;
sta->counters[WLANTEST_STA_COUNTER_PTK_LEARNED]++;
wpa_hexdump(MSG_DEBUG, "FILS: Decrypted Association Request elements",
buf, encr_end - frame_ad_end - AES_BLOCK_SIZE);
if (wt->write_pcap_dumper || wt->pcapng) {
write_pcap_decrypted(wt, frame_start,
frame_ad_end - frame_start,
buf,
encr_end - frame_ad_end - AES_BLOCK_SIZE);
}
return 0;
}
static void derive_fils_keys(struct wlantest *wt, struct wlantest_bss *bss,
struct wlantest_sta *sta, const u8 *frame_start,
const u8 *frame_ad, const u8 *frame_ad_end,
const u8 *encr_end)
{
struct wlantest_pmk *pmk;
wpa_printf(MSG_DEBUG, "Trying to derive PTK for " MACSTR
" from FILS rMSK", MAC2STR(sta->addr));
dl_list_for_each(pmk, &bss->pmk, struct wlantest_pmk,
list) {
wpa_printf(MSG_DEBUG, "Try per-BSS PMK");
if (try_rmsk(wt, bss, sta, pmk, frame_start, frame_ad,
frame_ad_end, encr_end) == 0)
return;
}
dl_list_for_each(pmk, &wt->pmk, struct wlantest_pmk, list) {
wpa_printf(MSG_DEBUG, "Try global PMK");
if (try_rmsk(wt, bss, sta, pmk, frame_start, frame_ad,
frame_ad_end, encr_end) == 0)
return;
}
}
static void rx_mgmt_assoc_req(struct wlantest *wt, const u8 *data, size_t len) static void rx_mgmt_assoc_req(struct wlantest *wt, const u8 *data, size_t len)
{ {
const struct ieee80211_mgmt *mgmt; const struct ieee80211_mgmt *mgmt;
struct wlantest_bss *bss; struct wlantest_bss *bss;
struct wlantest_sta *sta; struct wlantest_sta *sta;
struct ieee802_11_elems elems; struct ieee802_11_elems elems;
const u8 *ie;
size_t ie_len;
mgmt = (const struct ieee80211_mgmt *) data; mgmt = (const struct ieee80211_mgmt *) data;
bss = bss_get(wt, mgmt->bssid); bss = bss_get(wt, mgmt->bssid);
@ -286,9 +466,24 @@ static void rx_mgmt_assoc_req(struct wlantest *wt, const u8 *data, size_t len)
sta->counters[WLANTEST_STA_COUNTER_ASSOCREQ_TX]++; sta->counters[WLANTEST_STA_COUNTER_ASSOCREQ_TX]++;
if (ieee802_11_parse_elems(mgmt->u.assoc_req.variable, ie = mgmt->u.assoc_req.variable;
len - (mgmt->u.assoc_req.variable - data), ie_len = len - (mgmt->u.assoc_req.variable - data);
&elems, 0) == ParseFailed) {
if (sta->auth_alg == WLAN_AUTH_FILS_SK) {
const u8 *session, *frame_ad, *frame_ad_end, *encr_end;
session = get_fils_session(ie, ie_len);
if (session) {
frame_ad = (const u8 *) &mgmt->u.assoc_req.capab_info;
frame_ad_end = session + 2 + session[1];
encr_end = data + len;
derive_fils_keys(wt, bss, sta, data, frame_ad,
frame_ad_end, encr_end);
ie_len = session - ie;
}
}
if (ieee802_11_parse_elems(ie, ie_len, &elems, 0) == ParseFailed) {
add_note(wt, MSG_INFO, "Invalid IEs in Association Request " add_note(wt, MSG_INFO, "Invalid IEs in Association Request "
"frame from " MACSTR, MAC2STR(mgmt->sa)); "frame from " MACSTR, MAC2STR(mgmt->sa));
return; return;
@ -308,6 +503,65 @@ static void rx_mgmt_assoc_req(struct wlantest *wt, const u8 *data, size_t len)
} }
static void decrypt_fils_assoc_resp(struct wlantest *wt,
struct wlantest_bss *bss,
struct wlantest_sta *sta,
const u8 *frame_start, const u8 *frame_ad,
const u8 *frame_ad_end, const u8 *encr_end)
{
const u8 *aad[5];
size_t aad_len[5];
u8 buf[2000];
if (!sta->ptk_set)
return;
/* Check AES-SIV decryption with the derived key */
/* AES-SIV AAD vectors */
/* The AP's BSSID */
aad[0] = bss->bssid;
aad_len[0] = ETH_ALEN;
/* The STA's MAC address */
aad[1] = sta->addr;
aad_len[1] = ETH_ALEN;
/* The AP's nonce */
aad[2] = sta->anonce;
aad_len[2] = FILS_NONCE_LEN;
/* The STA's nonce */
aad[3] = sta->snonce;
aad_len[3] = FILS_NONCE_LEN;
/*
* The (Re)Association Response frame from the Capability Information
* field to the FILS Session element (both inclusive).
*/
aad[4] = frame_ad;
aad_len[4] = frame_ad_end - frame_ad;
if (encr_end - frame_ad_end < AES_BLOCK_SIZE ||
encr_end - frame_ad_end > sizeof(buf))
return;
if (aes_siv_decrypt(sta->ptk.kek, sta->ptk.kek_len,
frame_ad_end, encr_end - frame_ad_end,
5, aad, aad_len, buf) < 0) {
wpa_printf(MSG_DEBUG,
"FILS: Derived PTK did not match AES-SIV data");
return;
}
wpa_hexdump(MSG_DEBUG, "FILS: Decrypted Association Response elements",
buf, encr_end - frame_ad_end - AES_BLOCK_SIZE);
if (wt->write_pcap_dumper || wt->pcapng) {
write_pcap_decrypted(wt, frame_start,
frame_ad_end - frame_start,
buf,
encr_end - frame_ad_end - AES_BLOCK_SIZE);
}
}
static void rx_mgmt_assoc_resp(struct wlantest *wt, const u8 *data, size_t len) static void rx_mgmt_assoc_resp(struct wlantest *wt, const u8 *data, size_t len)
{ {
const struct ieee80211_mgmt *mgmt; const struct ieee80211_mgmt *mgmt;
@ -344,6 +598,20 @@ static void rx_mgmt_assoc_resp(struct wlantest *wt, const u8 *data, size_t len)
MAC2STR(mgmt->sa), MAC2STR(mgmt->da), capab, status, MAC2STR(mgmt->sa), MAC2STR(mgmt->da), capab, status,
aid & 0x3fff); aid & 0x3fff);
if (sta->auth_alg == WLAN_AUTH_FILS_SK) {
const u8 *session, *frame_ad, *frame_ad_end, *encr_end;
session = get_fils_session(ies, ies_len);
if (session) {
frame_ad = (const u8 *) &mgmt->u.assoc_resp.capab_info;
frame_ad_end = session + 2 + session[1];
encr_end = data + len;
decrypt_fils_assoc_resp(wt, bss, sta, data, frame_ad,
frame_ad_end, encr_end);
ies_len = session - ies;
}
}
if (status == WLAN_STATUS_ASSOC_REJECTED_TEMPORARILY) { if (status == WLAN_STATUS_ASSOC_REJECTED_TEMPORARILY) {
struct ieee802_11_elems elems; struct ieee802_11_elems elems;
if (ieee802_11_parse_elems(ies, ies_len, &elems, 0) == if (ieee802_11_parse_elems(ies, ies_len, &elems, 0) ==
@ -406,6 +674,8 @@ static void rx_mgmt_reassoc_req(struct wlantest *wt, const u8 *data,
struct wlantest_bss *bss; struct wlantest_bss *bss;
struct wlantest_sta *sta; struct wlantest_sta *sta;
struct ieee802_11_elems elems; struct ieee802_11_elems elems;
const u8 *ie;
size_t ie_len;
mgmt = (const struct ieee80211_mgmt *) data; mgmt = (const struct ieee80211_mgmt *) data;
bss = bss_get(wt, mgmt->bssid); bss = bss_get(wt, mgmt->bssid);
@ -430,9 +700,24 @@ static void rx_mgmt_reassoc_req(struct wlantest *wt, const u8 *data,
sta->counters[WLANTEST_STA_COUNTER_REASSOCREQ_TX]++; sta->counters[WLANTEST_STA_COUNTER_REASSOCREQ_TX]++;
if (ieee802_11_parse_elems(mgmt->u.reassoc_req.variable, ie = mgmt->u.reassoc_req.variable;
len - (mgmt->u.reassoc_req.variable - data), ie_len = len - (mgmt->u.reassoc_req.variable - data);
&elems, 0) == ParseFailed) {
if (sta->auth_alg == WLAN_AUTH_FILS_SK) {
const u8 *session, *frame_ad, *frame_ad_end, *encr_end;
session = get_fils_session(ie, ie_len);
if (session) {
frame_ad = (const u8 *) &mgmt->u.reassoc_req.capab_info;
frame_ad_end = session + 2 + session[1];
encr_end = data + len;
derive_fils_keys(wt, bss, sta, data, frame_ad,
frame_ad_end, encr_end);
ie_len = session - ie;
}
}
if (ieee802_11_parse_elems(ie, ie_len, &elems, 0) == ParseFailed) {
add_note(wt, MSG_INFO, "Invalid IEs in Reassociation Request " add_note(wt, MSG_INFO, "Invalid IEs in Reassociation Request "
"frame from " MACSTR, MAC2STR(mgmt->sa)); "frame from " MACSTR, MAC2STR(mgmt->sa));
return; return;
@ -460,6 +745,8 @@ static void rx_mgmt_reassoc_resp(struct wlantest *wt, const u8 *data,
struct wlantest_bss *bss; struct wlantest_bss *bss;
struct wlantest_sta *sta; struct wlantest_sta *sta;
u16 capab, status, aid; u16 capab, status, aid;
const u8 *ies;
size_t ies_len;
mgmt = (const struct ieee80211_mgmt *) data; mgmt = (const struct ieee80211_mgmt *) data;
bss = bss_get(wt, mgmt->bssid); bss = bss_get(wt, mgmt->bssid);
@ -475,6 +762,9 @@ static void rx_mgmt_reassoc_resp(struct wlantest *wt, const u8 *data,
return; return;
} }
ies = mgmt->u.reassoc_resp.variable;
ies_len = len - (mgmt->u.reassoc_resp.variable - data);
capab = le_to_host16(mgmt->u.reassoc_resp.capab_info); capab = le_to_host16(mgmt->u.reassoc_resp.capab_info);
status = le_to_host16(mgmt->u.reassoc_resp.status_code); status = le_to_host16(mgmt->u.reassoc_resp.status_code);
aid = le_to_host16(mgmt->u.reassoc_resp.aid); aid = le_to_host16(mgmt->u.reassoc_resp.aid);
@ -484,10 +774,24 @@ static void rx_mgmt_reassoc_resp(struct wlantest *wt, const u8 *data,
MAC2STR(mgmt->sa), MAC2STR(mgmt->da), capab, status, MAC2STR(mgmt->sa), MAC2STR(mgmt->da), capab, status,
aid & 0x3fff); aid & 0x3fff);
if (sta->auth_alg == WLAN_AUTH_FILS_SK) {
const u8 *session, *frame_ad, *frame_ad_end, *encr_end;
session = get_fils_session(ies, ies_len);
if (session) {
frame_ad = (const u8 *)
&mgmt->u.reassoc_resp.capab_info;
frame_ad_end = session + 2 + session[1];
encr_end = data + len;
decrypt_fils_assoc_resp(wt, bss, sta, data, frame_ad,
frame_ad_end, encr_end);
ies_len = session - ies;
}
}
if (status == WLAN_STATUS_ASSOC_REJECTED_TEMPORARILY) { if (status == WLAN_STATUS_ASSOC_REJECTED_TEMPORARILY) {
struct ieee802_11_elems elems; struct ieee802_11_elems elems;
const u8 *ies = mgmt->u.reassoc_resp.variable;
size_t ies_len = len - (mgmt->u.reassoc_resp.variable - data);
if (ieee802_11_parse_elems(ies, ies_len, &elems, 0) == if (ieee802_11_parse_elems(ies, ies_len, &elems, 0) ==
ParseFailed) { ParseFailed) {
add_note(wt, MSG_INFO, "Failed to parse IEs in " add_note(wt, MSG_INFO, "Failed to parse IEs in "

View file

@ -60,6 +60,7 @@ struct wlantest_sta {
STATE2 /* authenticated */, STATE2 /* authenticated */,
STATE3 /* associated */ STATE3 /* associated */
} state; } state;
u16 auth_alg;
u16 aid; u16 aid;
u8 rsnie[257]; /* WPA/RSN IE */ u8 rsnie[257]; /* WPA/RSN IE */
u8 osenie[257]; /* OSEN IE */ u8 osenie[257]; /* OSEN IE */