From b39f58347d1e27f145a71bb5e6cda5ebd7853877 Mon Sep 17 00:00:00 2001 From: Jouni Malinen Date: Mon, 13 Dec 2010 11:20:55 +0200 Subject: wlantest: Add support for decrypting TDLS frames Derive TPK based on TDLS TPK Handshake and decrypt frames on the direct link with TPK-TK. --- wlantest/Makefile | 1 + wlantest/bss.c | 4 + wlantest/rx_data.c | 49 ++++++-- wlantest/rx_tdls.c | 352 ++++++++++++++++++++++++++++++++++++++++++++++++++++ wlantest/wlantest.h | 17 +++ 5 files changed, 415 insertions(+), 8 deletions(-) create mode 100644 wlantest/rx_tdls.c (limited to 'wlantest') diff --git a/wlantest/Makefile b/wlantest/Makefile index 6d4416a..378efab 100644 --- a/wlantest/Makefile +++ b/wlantest/Makefile @@ -58,6 +58,7 @@ OBJS += rx_mgmt.o OBJS += rx_data.o OBJS += rx_eapol.o OBJS += rx_ip.o +OBJS += rx_tdls.o OBJS += bss.o OBJS += sta.o OBJS += crc32.o diff --git a/wlantest/bss.c b/wlantest/bss.c index 88064a5..e7942af 100644 --- a/wlantest/bss.c +++ b/wlantest/bss.c @@ -50,6 +50,7 @@ struct wlantest_bss * bss_get(struct wlantest *wt, const u8 *bssid) return NULL; dl_list_init(&bss->sta); dl_list_init(&bss->pmk); + dl_list_init(&bss->tdls); os_memcpy(bss->bssid, bssid, ETH_ALEN); dl_list_add(&wt->bss, &bss->list); wpa_printf(MSG_DEBUG, "Discovered new BSS - " MACSTR, @@ -69,10 +70,13 @@ void bss_deinit(struct wlantest_bss *bss) { struct wlantest_sta *sta, *n; struct wlantest_pmk *pmk, *np; + struct wlantest_tdls *tdls, *nt; dl_list_for_each_safe(sta, n, &bss->sta, struct wlantest_sta, list) sta_deinit(sta); dl_list_for_each_safe(pmk, np, &bss->pmk, struct wlantest_pmk, list) pmk_deinit(pmk); + dl_list_for_each_safe(tdls, nt, &bss->tdls, struct wlantest_tdls, list) + os_free(tdls); dl_list_del(&bss->list); os_free(bss); } diff --git a/wlantest/rx_data.c b/wlantest/rx_data.c index a344054..c094929 100644 --- a/wlantest/rx_data.c +++ b/wlantest/rx_data.c @@ -70,6 +70,9 @@ static void rx_data_eth(struct wlantest *wt, const u8 *bssid, case ETH_P_IP: rx_data_ip(wt, bssid, sta_addr, dst, src, data, len); break; + case 0x890d: + rx_data_80211_encap(wt, bssid, sta_addr, dst, src, data, len); + break; } } @@ -182,13 +185,15 @@ static void rx_data_bss_prot(struct wlantest *wt, size_t len) { struct wlantest_bss *bss; - struct wlantest_sta *sta; + struct wlantest_sta *sta, *sta2; int keyid; u16 fc = le_to_host16(hdr->frame_control); u8 *decrypted; size_t dlen; int tid; u8 pn[6], *rsc; + struct wlantest_tdls *tdls = NULL; + const u8 *tk = NULL; if (hdr->addr1[0] & 0x01) { rx_data_bss_prot_group(wt, hdr, qos, dst, src, data, len); @@ -200,13 +205,32 @@ static void rx_data_bss_prot(struct wlantest *wt, if (bss == NULL) return; sta = sta_get(bss, hdr->addr2); - } else { + } else if (fc & WLAN_FC_FROMDS) { bss = bss_get(wt, hdr->addr2); if (bss == NULL) return; sta = sta_get(bss, hdr->addr1); + } else { + bss = bss_get(wt, hdr->addr3); + if (bss == NULL) + return; + sta = sta_find(bss, hdr->addr2); + sta2 = sta_find(bss, hdr->addr1); + if (sta == NULL || sta2 == NULL) + return; + dl_list_for_each(tdls, &bss->tdls, struct wlantest_tdls, list) + { + if ((tdls->init == sta && tdls->resp == sta2) || + (tdls->init == sta2 && tdls->resp == sta)) { + if (!tdls->link_up) + wpa_printf(MSG_DEBUG, "TDLS: Link not " + "up, but Data frame seen"); + tk = tdls->tpk.tk; + break; + } + } } - if (sta == NULL || !sta->ptk_set) { + if ((sta == NULL || !sta->ptk_set) && tk == NULL) { wpa_printf(MSG_MSGDUMP, "No PTK known to decrypt the frame"); return; } @@ -224,7 +248,7 @@ static void rx_data_bss_prot(struct wlantest *wt, return; } - if (sta->pairwise_cipher == WPA_CIPHER_TKIP) { + if (tk == NULL && sta->pairwise_cipher == WPA_CIPHER_TKIP) { if (data[3] & 0x1f) { wpa_printf(MSG_INFO, "TKIP frame from " MACSTR " used " "non-zero reserved bit", @@ -237,7 +261,7 @@ static void rx_data_bss_prot(struct wlantest *wt, MAC2STR(hdr->addr2), data[1], (data[0] | 0x20) & 0x7f); } - } else if (sta->pairwise_cipher == WPA_CIPHER_CCMP) { + } else if (tk || sta->pairwise_cipher == WPA_CIPHER_CCMP) { if (data[2] != 0 || (data[3] & 0x1f) != 0) { wpa_printf(MSG_INFO, "CCMP frame from " MACSTR " used " "non-zero reserved bit", @@ -256,13 +280,18 @@ static void rx_data_bss_prot(struct wlantest *wt, tid = qos[0] & 0x0f; else tid = 0; - if (fc & WLAN_FC_TODS) + if (tk) { + if (os_memcmp(hdr->addr2, tdls->init->addr, ETH_ALEN) == 0) + rsc = tdls->rsc_init[tid]; + else + rsc = tdls->rsc_resp[tid]; + } else if (fc & WLAN_FC_TODS) rsc = sta->rsc_tods[tid]; else rsc = sta->rsc_fromds[tid]; - if (sta->pairwise_cipher == WPA_CIPHER_TKIP) + if (tk == NULL && sta->pairwise_cipher == WPA_CIPHER_TKIP) tkip_get_pn(pn, data); else ccmp_get_pn(pn, data); @@ -273,7 +302,9 @@ static void rx_data_bss_prot(struct wlantest *wt, wpa_hexdump(MSG_INFO, "RSC", rsc, 6); } - if (sta->pairwise_cipher == WPA_CIPHER_TKIP) + if (tk) + decrypted = ccmp_decrypt(tk, hdr, data, len, &dlen); + else if (sta->pairwise_cipher == WPA_CIPHER_TKIP) decrypted = tkip_decrypt(sta->ptk.tk1, hdr, data, len, &dlen); else decrypted = ccmp_decrypt(sta->ptk.tk1, hdr, data, len, &dlen); @@ -362,6 +393,8 @@ void rx_data(struct wlantest *wt, const u8 *data, size_t len) fc & WLAN_FC_ISWEP ? " Prot" : "", MAC2STR(hdr->addr1), MAC2STR(hdr->addr2), MAC2STR(hdr->addr3)); + rx_data_bss(wt, hdr, qos, hdr->addr1, hdr->addr2, + data + hdrlen, len - hdrlen); break; case WLAN_FC_FROMDS: wpa_printf(MSG_EXCESSIVE, "DATA %s%s%s FromDS DA=" MACSTR diff --git a/wlantest/rx_tdls.c b/wlantest/rx_tdls.c new file mode 100644 index 0000000..3b41b48 --- /dev/null +++ b/wlantest/rx_tdls.c @@ -0,0 +1,352 @@ +/* + * Received Data frame processing for TDLS packets + * Copyright (c) 2010, Jouni Malinen + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Alternatively, this software may be distributed under the terms of BSD + * license. + * + * See README and COPYING for more details. + */ + +#include "utils/includes.h" + +#include "utils/common.h" +#include "crypto/sha256.h" +#include "crypto/crypto.h" +#include "crypto/aes_wrap.h" +#include "common/ieee802_11_defs.h" +#include "common/ieee802_11_common.h" +#include "wlantest.h" + + +static struct wlantest_tdls * get_tdls(struct wlantest *wt, const u8 *linkid) +{ + struct wlantest_bss *bss; + struct wlantest_sta *init, *resp; + struct wlantest_tdls *tdls; + + bss = bss_find(wt, linkid); + if (bss == NULL) + return NULL; + + init = sta_find(bss, linkid + ETH_ALEN); + if (init == NULL) + return NULL; + + resp = sta_find(bss, linkid + 2 * ETH_ALEN); + if (resp == NULL) + return NULL; + + dl_list_for_each(tdls, &bss->tdls, struct wlantest_tdls, list) { + if (tdls->init == init && tdls->resp == resp) + return tdls; + } + + tdls = os_zalloc(sizeof(*tdls)); + if (tdls == NULL) + return NULL; + tdls->init = init; + tdls->resp = resp; + dl_list_add(&bss->tdls, &tdls->list); + return tdls; +} + + +static int tdls_derive_tpk(struct wlantest_tdls *tdls, const u8 *bssid, + const u8 *ftie, u8 ftie_len) +{ + const struct rsn_ftie *f; + u8 key_input[SHA256_MAC_LEN]; + const u8 *nonce[2]; + size_t len[2]; + u8 data[3 * ETH_ALEN]; + + if (ftie == NULL || ftie_len < sizeof(struct rsn_ftie)) + return 0; + + f = (const struct rsn_ftie *) ftie; + wpa_hexdump(MSG_DEBUG, "TDLS ANonce", f->anonce, WPA_NONCE_LEN); + wpa_hexdump(MSG_DEBUG, "TDLS SNonce", f->snonce, WPA_NONCE_LEN); + + /* + * IEEE Std 802.11z-2010 8.5.9.1: + * TPK-Key-Input = SHA-256(min(SNonce, ANonce) || max(SNonce, ANonce)) + */ + len[0] = WPA_NONCE_LEN; + len[1] = WPA_NONCE_LEN; + if (os_memcmp(f->anonce, f->snonce, WPA_NONCE_LEN) < 0) { + nonce[0] = f->anonce; + nonce[1] = f->snonce; + } else { + nonce[0] = f->snonce; + nonce[1] = f->anonce; + } + sha256_vector(2, nonce, len, key_input); + wpa_hexdump_key(MSG_DEBUG, "TDLS: TPK-Key-Input", + key_input, SHA256_MAC_LEN); + + /* + * TPK-Key-Data = KDF-N_KEY(TPK-Key-Input, "TDLS PMK", + * min(MAC_I, MAC_R) || max(MAC_I, MAC_R) || BSSID || N_KEY) + * TODO: is N_KEY really included in KDF Context and if so, in which + * presentation format (little endian 16-bit?) is it used? It gets + * added by the KDF anyway.. + */ + + if (os_memcmp(tdls->init->addr, tdls->resp->addr, ETH_ALEN) < 0) { + os_memcpy(data, tdls->init->addr, ETH_ALEN); + os_memcpy(data + ETH_ALEN, tdls->resp->addr, ETH_ALEN); + } else { + os_memcpy(data, tdls->resp->addr, ETH_ALEN); + os_memcpy(data + ETH_ALEN, tdls->init->addr, ETH_ALEN); + } + os_memcpy(data + 2 * ETH_ALEN, bssid, ETH_ALEN); + wpa_hexdump(MSG_DEBUG, "TDLS: KDF Context", data, sizeof(data)); + + sha256_prf(key_input, SHA256_MAC_LEN, "TDLS PMK", data, sizeof(data), + (u8 *) &tdls->tpk, sizeof(tdls->tpk)); + wpa_hexdump_key(MSG_DEBUG, "TDLS: TPK-KCK", + tdls->tpk.kck, sizeof(tdls->tpk.kck)); + wpa_hexdump_key(MSG_DEBUG, "TDLS: TPK-TK", + tdls->tpk.tk, sizeof(tdls->tpk.tk)); + + return 1; +} + + +static int tdls_verify_mic(struct wlantest_tdls *tdls, u8 trans_seq, + struct ieee802_11_elems *elems) +{ + u8 *buf, *pos; + int len; + u8 mic[16]; + int ret; + const struct rsn_ftie *rx_ftie; + struct rsn_ftie *tmp_ftie; + + if (elems->link_id == NULL || elems->rsn_ie == NULL || + elems->timeout_int == NULL || elems->ftie == NULL) + return -1; + + len = 2 * ETH_ALEN + 1 + 2 + 18 + 2 + elems->rsn_ie_len + + 2 + elems->timeout_int_len + 2 + elems->ftie_len; + + buf = os_zalloc(len); + if (buf == NULL) + return -1; + + pos = buf; + /* 1) TDLS initiator STA MAC address */ + os_memcpy(pos, elems->link_id + ETH_ALEN, ETH_ALEN); + pos += ETH_ALEN; + /* 2) TDLS responder STA MAC address */ + os_memcpy(pos, elems->link_id + 2 * ETH_ALEN, ETH_ALEN); + pos += ETH_ALEN; + /* 3) Transaction Sequence number */ + *pos++ = trans_seq; + /* 4) Link Identifier IE */ + os_memcpy(pos, elems->link_id - 2, 2 + 18); + pos += 2 + 18; + /* 5) RSN IE */ + os_memcpy(pos, elems->rsn_ie - 2, 2 + elems->rsn_ie_len); + pos += 2 + elems->rsn_ie_len; + /* 6) Timeout Interval IE */ + os_memcpy(pos, elems->timeout_int - 2, 2 + elems->timeout_int_len); + pos += 2 + elems->timeout_int_len; + /* 7) FTIE, with the MIC field of the FTIE set to 0 */ + os_memcpy(pos, elems->ftie - 2, 2 + elems->ftie_len); + pos += 2; + tmp_ftie = (struct rsn_ftie *) pos; + os_memset(tmp_ftie->mic, 0, 16); + pos += elems->ftie_len; + + wpa_hexdump(MSG_DEBUG, "TDLS: Data for FTIE MIC", buf, pos - buf); + wpa_hexdump_key(MSG_DEBUG, "TDLS: KCK", tdls->tpk.kck, 16); + ret = omac1_aes_128(tdls->tpk.kck, buf, pos - buf, mic); + os_free(buf); + if (ret) + return -1; + wpa_hexdump(MSG_DEBUG, "TDLS: FTIE MIC", mic, 16); + rx_ftie = (const struct rsn_ftie *) elems->ftie; + + if (os_memcmp(mic, rx_ftie->mic, 16) == 0) { + wpa_printf(MSG_DEBUG, "TDLS: Valid MIC"); + return 0; + } + wpa_printf(MSG_DEBUG, "TDLS: Invalid MIC"); + return -1; +} + + +static void rx_data_tdls_setup_request(struct wlantest *wt, const u8 *bssid, + const u8 *sta_addr, const u8 *dst, + const u8 *src, + const u8 *data, size_t len) +{ + struct ieee802_11_elems elems; + + if (len < 3) + return; + wpa_printf(MSG_DEBUG, "TDLS Setup Request " MACSTR " -> " + MACSTR, MAC2STR(src), MAC2STR(dst)); + + if (ieee802_11_parse_elems(data + 3, len - 3, &elems, 1) == + ParseFailed || elems.link_id == NULL) + return; + wpa_printf(MSG_DEBUG, "TDLS Link Identifier: BSSID " MACSTR + " initiator STA " MACSTR " responder STA " MACSTR, + MAC2STR(elems.link_id), MAC2STR(elems.link_id + ETH_ALEN), + MAC2STR(elems.link_id + 2 * ETH_ALEN)); +} + + +static void rx_data_tdls_setup_response(struct wlantest *wt, const u8 *bssid, + const u8 *sta_addr, const u8 *dst, + const u8 *src, + const u8 *data, size_t len) +{ + u16 status; + struct ieee802_11_elems elems; + struct wlantest_tdls *tdls; + + if (len < 5) + return; + status = WPA_GET_LE16(data); + wpa_printf(MSG_DEBUG, "TDLS Setup Response " MACSTR " -> " + MACSTR " (status %d)", + MAC2STR(src), MAC2STR(dst), status); + if (status != WLAN_STATUS_SUCCESS) + return; + + if (ieee802_11_parse_elems(data + 5, len - 5, &elems, 1) == + ParseFailed || elems.link_id == NULL) + return; + wpa_printf(MSG_DEBUG, "TDLS Link Identifier: BSSID " MACSTR + " initiator STA " MACSTR " responder STA " MACSTR, + MAC2STR(elems.link_id), MAC2STR(elems.link_id + ETH_ALEN), + MAC2STR(elems.link_id + 2 * ETH_ALEN)); + + tdls = get_tdls(wt, elems.link_id); + if (!tdls) + return; + if (tdls_derive_tpk(tdls, bssid, elems.ftie, elems.ftie_len) < 1) + return; + tdls_verify_mic(tdls, 2, &elems); +} + + +static void rx_data_tdls_setup_confirm(struct wlantest *wt, const u8 *bssid, + const u8 *sta_addr, const u8 *dst, + const u8 *src, + const u8 *data, size_t len) +{ + u16 status; + struct ieee802_11_elems elems; + struct wlantest_tdls *tdls; + + if (len < 3) + return; + status = WPA_GET_LE16(data); + wpa_printf(MSG_DEBUG, "TDLS Setup Confirm " MACSTR " -> " + MACSTR " (status %d)", + MAC2STR(src), MAC2STR(dst), status); + if (status != WLAN_STATUS_SUCCESS) + return; + + if (ieee802_11_parse_elems(data + 3, len - 3, &elems, 1) == + ParseFailed || elems.link_id == NULL) + return; + wpa_printf(MSG_DEBUG, "TDLS Link Identifier: BSSID " MACSTR + " initiator STA " MACSTR " responder STA " MACSTR, + MAC2STR(elems.link_id), MAC2STR(elems.link_id + ETH_ALEN), + MAC2STR(elems.link_id + 2 * ETH_ALEN)); + + tdls = get_tdls(wt, elems.link_id); + if (tdls) + tdls->link_up = 1; + if (tdls_derive_tpk(tdls, bssid, elems.ftie, elems.ftie_len) < 1) + return; + tdls_verify_mic(tdls, 3, &elems); +} + + +static void rx_data_tdls_teardown(struct wlantest *wt, const u8 *bssid, + const u8 *sta_addr, const u8 *dst, + const u8 *src, + const u8 *data, size_t len) +{ + u16 reason; + struct ieee802_11_elems elems; + struct wlantest_tdls *tdls; + + if (len < 2) + return; + reason = WPA_GET_LE16(data); + wpa_printf(MSG_DEBUG, "TDLS Teardown " MACSTR " -> " + MACSTR " (reason %d)", + MAC2STR(src), MAC2STR(dst), reason); + + if (ieee802_11_parse_elems(data + 2, len - 2, &elems, 1) == + ParseFailed || elems.link_id == NULL) + return; + wpa_printf(MSG_DEBUG, "TDLS Link Identifier: BSSID " MACSTR + " initiator STA " MACSTR " responder STA " MACSTR, + MAC2STR(elems.link_id), MAC2STR(elems.link_id + ETH_ALEN), + MAC2STR(elems.link_id + 2 * ETH_ALEN)); + + tdls = get_tdls(wt, elems.link_id); + if (tdls) + tdls->link_up = 0; +} + + +static void rx_data_tdls(struct wlantest *wt, const u8 *bssid, + const u8 *sta_addr, const u8 *dst, const u8 *src, + const u8 *data, size_t len) +{ + /* data contains the payload of a TDLS Action frame */ + if (len < 2 || data[0] != WLAN_ACTION_TDLS) { + wpa_hexdump(MSG_DEBUG, "Unrecognized encapsulated TDLS frame", + data, len); + return; + } + + switch (data[1]) { + case WLAN_TDLS_SETUP_REQUEST: + rx_data_tdls_setup_request(wt, bssid, sta_addr, dst, src, + data + 2, len - 2); + break; + case WLAN_TDLS_SETUP_RESPONSE: + rx_data_tdls_setup_response(wt, bssid, sta_addr, dst, src, + data + 2, len - 2); + break; + case WLAN_TDLS_SETUP_CONFIRM: + rx_data_tdls_setup_confirm(wt, bssid, sta_addr, dst, src, + data + 2, len - 2); + break; + case WLAN_TDLS_TEARDOWN: + rx_data_tdls_teardown(wt, bssid, sta_addr, dst, src, data + 2, + len - 2); + break; + case WLAN_TDLS_DISCOVERY_REQUEST: + wpa_printf(MSG_DEBUG, "TDLS Discovery Request " MACSTR " -> " + MACSTR, MAC2STR(src), MAC2STR(dst)); + break; + } +} + + +void rx_data_80211_encap(struct wlantest *wt, const u8 *bssid, + const u8 *sta_addr, const u8 *dst, const u8 *src, + const u8 *data, size_t len) +{ + wpa_hexdump(MSG_EXCESSIVE, "802.11 data encap frame", data, len); + if (len < 1) + return; + if (data[0] == 0x02) + rx_data_tdls(wt, bssid, sta_addr, dst, src, data + 1, len - 1); +} diff --git a/wlantest/wlantest.h b/wlantest/wlantest.h index 35e5135..be5406f 100644 --- a/wlantest/wlantest.h +++ b/wlantest/wlantest.h @@ -84,6 +84,19 @@ struct wlantest_sta { le16 seq_ctrl_to_ap[17]; }; +struct wlantest_tdls { + struct dl_list list; + struct wlantest_sta *init; + struct wlantest_sta *resp; + struct tpk { + u8 kck[16]; + u8 tk[16]; + } tpk; + int link_up; + u8 rsc_init[16 + 1][6]; + u8 rsc_resp[16 + 1][6]; +}; + struct wlantest_bss { struct dl_list list; u8 bssid[ETH_ALEN]; @@ -111,6 +124,7 @@ struct wlantest_bss { int igtk_idx; u8 ipn[6][6]; u32 counters[NUM_WLANTEST_BSS_COUNTER]; + struct dl_list tdls; /* struct wlantest_tdls */ }; struct wlantest_radius { @@ -167,6 +181,9 @@ void rx_data_eapol(struct wlantest *wt, const u8 *dst, const u8 *src, const u8 *data, size_t len, int prot); void rx_data_ip(struct wlantest *wt, const u8 *bssid, const u8 *sta_addr, const u8 *dst, const u8 *src, const u8 *data, size_t len); +void rx_data_80211_encap(struct wlantest *wt, const u8 *bssid, + const u8 *sta_addr, const u8 *dst, const u8 *src, + const u8 *data, size_t len); struct wlantest_bss * bss_find(struct wlantest *wt, const u8 *bssid); struct wlantest_bss * bss_get(struct wlantest *wt, const u8 *bssid); -- cgit v1.1