wlantest: Validate FT elements in Reassociation Response frame

Verify that RSNE, MDE, and FTE have valid information in FT
Reassociation Response frames. In addition, decrypt GTK, IGTK, and BIGTK
from the frame.

Signed-off-by: Jouni Malinen <j@w1.fi>
master
Jouni Malinen 4 years ago
parent e10144c910
commit b6a3bcffd7

@ -1055,6 +1055,228 @@ static void rx_mgmt_reassoc_req(struct wlantest *wt, const u8 *data,
}
static void process_gtk_subelem(struct wlantest *wt, struct wlantest_bss *bss,
struct wlantest_sta *sta,
const u8 *kek, size_t kek_len,
const u8 *gtk_elem,
size_t gtk_elem_len)
{
u8 gtk[32];
int keyidx;
enum wpa_alg alg;
size_t gtk_len, keylen;
const u8 *rsc;
if (!gtk_elem) {
add_note(wt, MSG_INFO, "FT: No GTK included in FTE");
return;
}
wpa_hexdump(MSG_DEBUG, "FT: Received GTK in Reassoc Resp",
gtk_elem, gtk_elem_len);
if (gtk_elem_len < 11 + 24 || (gtk_elem_len - 11) % 8 ||
gtk_elem_len - 19 > sizeof(gtk)) {
add_note(wt, MSG_INFO, "FT: Invalid GTK sub-elem length %zu",
gtk_elem_len);
return;
}
gtk_len = gtk_elem_len - 19;
if (aes_unwrap(kek, kek_len, gtk_len / 8, gtk_elem + 11, gtk)) {
add_note(wt, MSG_INFO,
"FT: AES unwrap failed - could not decrypt GTK");
return;
}
keylen = wpa_cipher_key_len(bss->group_cipher);
alg = wpa_cipher_to_alg(bss->group_cipher);
if (alg == WPA_ALG_NONE) {
add_note(wt, MSG_INFO, "FT: Unsupported Group Cipher %d",
bss->group_cipher);
return;
}
if (gtk_len < keylen) {
add_note(wt, MSG_INFO, "FT: Too short GTK in FTE");
return;
}
/* Key Info[2] | Key Length[1] | RSC[8] | Key[5..32]. */
keyidx = WPA_GET_LE16(gtk_elem) & 0x03;
if (gtk_elem[2] != keylen) {
add_note(wt, MSG_INFO,
"FT: GTK length mismatch: received %u negotiated %zu",
gtk_elem[2], keylen);
return;
}
add_note(wt, MSG_DEBUG, "GTK KeyID=%u", keyidx);
wpa_hexdump(MSG_DEBUG, "FT: GTK from Reassoc Resp", gtk, keylen);
if (bss->group_cipher == WPA_CIPHER_TKIP) {
/* Swap Tx/Rx keys for Michael MIC */
u8 tmp[8];
os_memcpy(tmp, gtk + 16, 8);
os_memcpy(gtk + 16, gtk + 24, 8);
os_memcpy(gtk + 24, tmp, 8);
}
bss->gtk_len[keyidx] = gtk_len;
sta->gtk_len = gtk_len;
os_memcpy(bss->gtk[keyidx], gtk, gtk_len);
os_memcpy(sta->gtk, gtk, gtk_len);
rsc = gtk_elem + 2;
bss->rsc[keyidx][0] = rsc[5];
bss->rsc[keyidx][1] = rsc[4];
bss->rsc[keyidx][2] = rsc[3];
bss->rsc[keyidx][3] = rsc[2];
bss->rsc[keyidx][4] = rsc[1];
bss->rsc[keyidx][5] = rsc[0];
bss->gtk_idx = keyidx;
sta->gtk_idx = keyidx;
wpa_hexdump(MSG_DEBUG, "RSC", bss->rsc[keyidx], 6);
}
static void process_igtk_subelem(struct wlantest *wt, struct wlantest_bss *bss,
struct wlantest_sta *sta,
const u8 *kek, size_t kek_len,
const u8 *igtk_elem, size_t igtk_elem_len)
{
u8 igtk[WPA_IGTK_MAX_LEN];
size_t igtk_len;
u16 keyidx;
const u8 *ipn;
if (bss->mgmt_group_cipher != WPA_CIPHER_AES_128_CMAC &&
bss->mgmt_group_cipher != WPA_CIPHER_BIP_GMAC_128 &&
bss->mgmt_group_cipher != WPA_CIPHER_BIP_GMAC_256 &&
bss->mgmt_group_cipher != WPA_CIPHER_BIP_CMAC_256)
return;
if (!igtk_elem) {
add_note(wt, MSG_INFO, "FT: No IGTK included in FTE");
return;
}
wpa_hexdump(MSG_DEBUG, "FT: Received IGTK in Reassoc Resp",
igtk_elem, igtk_elem_len);
igtk_len = wpa_cipher_key_len(bss->mgmt_group_cipher);
if (igtk_elem_len != 2 + 6 + 1 + igtk_len + 8) {
add_note(wt, MSG_INFO, "FT: Invalid IGTK sub-elem length %zu",
igtk_elem_len);
return;
}
if (igtk_elem[8] != igtk_len) {
add_note(wt, MSG_INFO,
"FT: Invalid IGTK sub-elem Key Length %d",
igtk_elem[8]);
return;
}
if (aes_unwrap(kek, kek_len, igtk_len / 8, igtk_elem + 9, igtk)) {
add_note(wt, MSG_INFO,
"FT: AES unwrap failed - could not decrypt IGTK");
return;
}
/* KeyID[2] | IPN[6] | Key Length[1] | Key[16+8] */
keyidx = WPA_GET_LE16(igtk_elem);
wpa_hexdump(MSG_DEBUG, "FT: IGTK from Reassoc Resp", igtk, igtk_len);
if (keyidx < 4 || keyidx > 5) {
add_note(wt, MSG_INFO, "Unexpected IGTK KeyID %u", keyidx);
return;
}
add_note(wt, MSG_DEBUG, "IGTK KeyID %u", keyidx);
wpa_hexdump(MSG_DEBUG, "IPN", igtk_elem + 2, 6);
wpa_hexdump(MSG_DEBUG, "IGTK", igtk, igtk_len);
os_memcpy(bss->igtk[keyidx], igtk, igtk_len);
bss->igtk_len[keyidx] = igtk_len;
ipn = igtk_elem + 2;
bss->ipn[keyidx][0] = ipn[5];
bss->ipn[keyidx][1] = ipn[4];
bss->ipn[keyidx][2] = ipn[3];
bss->ipn[keyidx][3] = ipn[2];
bss->ipn[keyidx][4] = ipn[1];
bss->ipn[keyidx][5] = ipn[0];
bss->igtk_idx = keyidx;
}
static void process_bigtk_subelem(struct wlantest *wt, struct wlantest_bss *bss,
struct wlantest_sta *sta,
const u8 *kek, size_t kek_len,
const u8 *bigtk_elem, size_t bigtk_elem_len)
{
u8 bigtk[WPA_BIGTK_MAX_LEN];
size_t bigtk_len;
u16 keyidx;
const u8 *ipn;
if (!bigtk_elem ||
(bss->mgmt_group_cipher != WPA_CIPHER_AES_128_CMAC &&
bss->mgmt_group_cipher != WPA_CIPHER_BIP_GMAC_128 &&
bss->mgmt_group_cipher != WPA_CIPHER_BIP_GMAC_256 &&
bss->mgmt_group_cipher != WPA_CIPHER_BIP_CMAC_256))
return;
wpa_hexdump_key(MSG_DEBUG, "FT: Received BIGTK in Reassoc Resp",
bigtk_elem, bigtk_elem_len);
bigtk_len = wpa_cipher_key_len(bss->mgmt_group_cipher);
if (bigtk_elem_len != 2 + 6 + 1 + bigtk_len + 8) {
add_note(wt, MSG_INFO,
"FT: Invalid BIGTK sub-elem length %zu",
bigtk_elem_len);
return;
}
if (bigtk_elem[8] != bigtk_len) {
add_note(wt, MSG_INFO,
"FT: Invalid BIGTK sub-elem Key Length %d",
bigtk_elem[8]);
return;
}
if (aes_unwrap(kek, kek_len, bigtk_len / 8, bigtk_elem + 9, bigtk)) {
add_note(wt, MSG_INFO,
"FT: AES unwrap failed - could not decrypt BIGTK");
return;
}
/* KeyID[2] | IPN[6] | Key Length[1] | Key[16+8] */
keyidx = WPA_GET_LE16(bigtk_elem);
wpa_hexdump(MSG_DEBUG, "FT: BIGTK from Reassoc Resp", bigtk, bigtk_len);
if (keyidx < 6 || keyidx > 7) {
add_note(wt, MSG_INFO, "Unexpected BIGTK KeyID %u", keyidx);
return;
}
add_note(wt, MSG_DEBUG, "BIGTK KeyID %u", keyidx);
wpa_hexdump(MSG_DEBUG, "BIPN", bigtk_elem + 2, 6);
wpa_hexdump(MSG_DEBUG, "BIGTK", bigtk, bigtk_len);
os_memcpy(bss->igtk[keyidx], bigtk, bigtk_len);
bss->igtk_len[keyidx] = bigtk_len;
ipn = bigtk_elem + 2;
bss->ipn[keyidx][0] = ipn[5];
bss->ipn[keyidx][1] = ipn[4];
bss->ipn[keyidx][2] = ipn[3];
bss->ipn[keyidx][3] = ipn[2];
bss->ipn[keyidx][4] = ipn[1];
bss->ipn[keyidx][5] = ipn[0];
bss->bigtk_idx = keyidx;
}
static void rx_mgmt_reassoc_resp(struct wlantest *wt, const u8 *data,
size_t len)
{
@ -1064,6 +1286,7 @@ static void rx_mgmt_reassoc_resp(struct wlantest *wt, const u8 *data,
u16 capab, status, aid;
const u8 *ies;
size_t ies_len;
struct ieee802_11_elems elems;
mgmt = (const struct ieee80211_mgmt *) data;
bss = bss_get(wt, mgmt->bssid);
@ -1106,17 +1329,15 @@ static void rx_mgmt_reassoc_resp(struct wlantest *wt, const u8 *data,
}
}
if (status == WLAN_STATUS_ASSOC_REJECTED_TEMPORARILY) {
struct ieee802_11_elems elems;
if (ieee802_11_parse_elems(ies, ies_len, &elems, 0) == ParseFailed) {
add_note(wt, MSG_INFO,
"Failed to parse IEs in ReassocResp from " MACSTR,
MAC2STR(mgmt->sa));
}
if (ieee802_11_parse_elems(ies, ies_len, &elems, 0) ==
ParseFailed) {
add_note(wt, MSG_INFO, "Failed to parse IEs in "
"ReassocResp from " MACSTR,
MAC2STR(mgmt->sa));
} else if (elems.timeout_int == NULL ||
elems.timeout_int[0] !=
WLAN_TIMEOUT_ASSOC_COMEBACK) {
if (status == WLAN_STATUS_ASSOC_REJECTED_TEMPORARILY) {
if (!elems.timeout_int ||
elems.timeout_int[0] != WLAN_TIMEOUT_ASSOC_COMEBACK) {
add_note(wt, MSG_INFO, "No valid Timeout Interval IE "
"with Assoc Comeback time in ReassocResp "
"(status=30) from " MACSTR,
@ -1149,6 +1370,212 @@ static void rx_mgmt_reassoc_resp(struct wlantest *wt, const u8 *data,
MAC2STR(sta->addr), MAC2STR(bss->bssid));
sta->state = STATE3;
}
if (elems.ftie) {
struct wpa_ft_ies parse;
int use_sha384;
struct rsn_mdie *mde;
const u8 *anonce, *snonce, *fte_mic;
u8 fte_elem_count;
unsigned int count;
u8 mic[WPA_EAPOL_KEY_MIC_MAX_LEN];
size_t mic_len = 16;
const u8 *kck, *kek;
size_t kck_len, kek_len;
use_sha384 = wpa_key_mgmt_sha384(sta->key_mgmt);
if (wpa_ft_parse_ies(ies, ies_len, &parse, use_sha384) < 0) {
add_note(wt, MSG_INFO, "FT: Failed to parse FT IEs");
return;
}
if (!parse.rsn) {
add_note(wt, MSG_INFO, "FT: No RSNE in Reassoc Resp");
return;
}
if (!parse.rsn_pmkid) {
add_note(wt, MSG_INFO, "FT: No PMKID in RSNE");
return;
}
if (os_memcmp_const(parse.rsn_pmkid, sta->pmk_r1_name,
WPA_PMK_NAME_LEN) != 0) {
add_note(wt, MSG_INFO,
"FT: PMKID in Reassoc Resp did not match PMKR1Name");
wpa_hexdump(MSG_DEBUG,
"FT: Received RSNE[PMKR1Name]",
parse.rsn_pmkid, WPA_PMK_NAME_LEN);
wpa_hexdump(MSG_DEBUG,
"FT: Previously derived PMKR1Name",
sta->pmk_r1_name, WPA_PMK_NAME_LEN);
return;
}
mde = (struct rsn_mdie *) parse.mdie;
if (!mde || parse.mdie_len < sizeof(*mde) ||
os_memcmp(mde->mobility_domain, bss->mdid,
MOBILITY_DOMAIN_ID_LEN) != 0) {
add_note(wt, MSG_INFO, "FT: Invalid MDE");
}
if (use_sha384) {
struct rsn_ftie_sha384 *fte;
fte = (struct rsn_ftie_sha384 *) parse.ftie;
if (!fte || parse.ftie_len < sizeof(*fte)) {
add_note(wt, MSG_INFO, "FT: Invalid FTE");
return;
}
anonce = fte->anonce;
snonce = fte->snonce;
fte_elem_count = fte->mic_control[1];
fte_mic = fte->mic;
} else {
struct rsn_ftie *fte;
fte = (struct rsn_ftie *) parse.ftie;
if (!fte || parse.ftie_len < sizeof(*fte)) {
add_note(wt, MSG_INFO, "FT: Invalid FTIE");
return;
}
anonce = fte->anonce;
snonce = fte->snonce;
fte_elem_count = fte->mic_control[1];
fte_mic = fte->mic;
}
if (os_memcmp(snonce, sta->snonce, WPA_NONCE_LEN) != 0) {
add_note(wt, MSG_INFO, "FT: SNonce mismatch in FTIE");
wpa_hexdump(MSG_DEBUG, "FT: Received SNonce",
snonce, WPA_NONCE_LEN);
wpa_hexdump(MSG_DEBUG, "FT: Expected SNonce",
sta->snonce, WPA_NONCE_LEN);
return;
}
if (os_memcmp(anonce, sta->anonce, WPA_NONCE_LEN) != 0) {
add_note(wt, MSG_INFO, "FT: ANonce mismatch in FTIE");
wpa_hexdump(MSG_DEBUG, "FT: Received ANonce",
anonce, WPA_NONCE_LEN);
wpa_hexdump(MSG_DEBUG, "FT: Expected ANonce",
sta->anonce, WPA_NONCE_LEN);
return;
}
if (!parse.r0kh_id) {
add_note(wt, MSG_INFO, "FT: No R0KH-ID subelem in FTE");
return;
}
if (parse.r0kh_id_len != bss->r0kh_id_len ||
os_memcmp_const(parse.r0kh_id, bss->r0kh_id,
parse.r0kh_id_len) != 0) {
add_note(wt, MSG_INFO,
"FT: R0KH-ID in FTE did not match the current R0KH-ID");
wpa_hexdump(MSG_DEBUG, "FT: R0KH-ID in FTIE",
parse.r0kh_id, parse.r0kh_id_len);
wpa_hexdump(MSG_DEBUG, "FT: The current R0KH-ID",
bss->r0kh_id, bss->r0kh_id_len);
os_memcpy(bss->r0kh_id, parse.r0kh_id,
parse.r0kh_id_len);
bss->r0kh_id_len = parse.r0kh_id_len;
}
if (!parse.r1kh_id) {
add_note(wt, MSG_INFO, "FT: No R1KH-ID subelem in FTE");
return;
}
if (os_memcmp_const(parse.r1kh_id, bss->r1kh_id,
FT_R1KH_ID_LEN) != 0) {
add_note(wt, MSG_INFO,
"FT: Unknown R1KH-ID used in ReassocResp");
os_memcpy(bss->r1kh_id, parse.r1kh_id, FT_R1KH_ID_LEN);
}
count = 3;
if (parse.ric)
count += ieee802_11_ie_count(parse.ric, parse.ric_len);
if (parse.rsnxe)
count++;
if (fte_elem_count != count) {
add_note(wt, MSG_INFO,
"FT: Unexpected IE count in MIC Control: received %u expected %u",
fte_elem_count, count);
return;
}
if (wpa_key_mgmt_fils(sta->key_mgmt)) {
kck = sta->ptk.kck2;
kck_len = sta->ptk.kck2_len;
kek = sta->ptk.kek2;
kek_len = sta->ptk.kek2_len;
} else {
kck = sta->ptk.kck;
kck_len = sta->ptk.kck_len;
kek = sta->ptk.kek;
kek_len = sta->ptk.kek_len;
}
if (wpa_ft_mic(kck, kck_len, sta->addr, bss->bssid, 6,
parse.mdie - 2, parse.mdie_len + 2,
parse.ftie - 2, parse.ftie_len + 2,
parse.rsn - 2, parse.rsn_len + 2,
parse.ric, parse.ric_len,
parse.rsnxe ? parse.rsnxe - 2 : NULL,
parse.rsnxe ? parse.rsnxe_len + 2 : 0,
mic) < 0) {
add_note(wt, MSG_INFO, "FT: Failed to calculate MIC");
return;
}
if (os_memcmp_const(mic, fte_mic, mic_len) != 0) {
add_note(wt, MSG_INFO, "FT: Invalid MIC in FTE");
wpa_printf(MSG_DEBUG,
"FT: addr=" MACSTR " auth_addr=" MACSTR,
MAC2STR(sta->addr),
MAC2STR(bss->bssid));
wpa_hexdump(MSG_MSGDUMP, "FT: Received MIC",
fte_mic, mic_len);
wpa_hexdump(MSG_MSGDUMP, "FT: Calculated MIC",
mic, mic_len);
wpa_hexdump(MSG_MSGDUMP, "FT: MDE",
parse.mdie - 2, parse.mdie_len + 2);
wpa_hexdump(MSG_MSGDUMP, "FT: FTE",
parse.ftie - 2, parse.ftie_len + 2);
wpa_hexdump(MSG_MSGDUMP, "FT: RSN",
parse.rsn - 2, parse.rsn_len + 2);
wpa_hexdump(MSG_MSGDUMP, "FT: RSNXE",
parse.rsnxe ? parse.rsnxe - 2 : NULL,
parse.rsnxe ? parse.rsnxe_len + 2 : 0);
return;
}
add_note(wt, MSG_INFO, "FT: Valid FTE MIC");
if (wpa_compare_rsn_ie(wpa_key_mgmt_ft(sta->key_mgmt),
bss->rsnie, 2 + bss->rsnie[1],
parse.rsn - 2, parse.rsn_len + 2)) {
add_note(wt, MSG_INFO,
"FT: RSNE mismatch between Beacon/ProbeResp and FT protocol Reassociation Response frame");
wpa_hexdump(MSG_INFO, "RSNE in Beacon/ProbeResp",
&bss->rsnie[2], bss->rsnie[1]);
wpa_hexdump(MSG_INFO,
"RSNE in FT protocol Reassociation Response frame",
parse.rsn ? parse.rsn - 2 : NULL,
parse.rsn ? parse.rsn_len + 2 : 0);
}
process_gtk_subelem(wt, bss, sta, kek, kek_len,
parse.gtk, parse.gtk_len);
process_igtk_subelem(wt, bss, sta, kek, kek_len,
parse.igtk, parse.igtk_len);
process_bigtk_subelem(wt, bss, sta, kek, kek_len,
parse.bigtk, parse.bigtk_len);
}
}

Loading…
Cancel
Save