diff --git a/wlantest/Makefile b/wlantest/Makefile index 546abe1e8..c8ba66540 100644 --- a/wlantest/Makefile +++ b/wlantest/Makefile @@ -58,6 +58,7 @@ OBJS += rx_data.o OBJS += bss.o OBJS += sta.o OBJS += crc32.o +OBJS += ccmp.o LIBS += -lpcap diff --git a/wlantest/ccmp.c b/wlantest/ccmp.c new file mode 100644 index 000000000..c6da41ad3 --- /dev/null +++ b/wlantest/ccmp.c @@ -0,0 +1,213 @@ +/* + * CTR with CBC-MAC Protocol (CCMP) + * 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 "common/ieee802_11_defs.h" +#include "crypto/aes.h" +#include "wlantest.h" + + +static void ccmp_aad_nonce(const struct ieee80211_hdr *hdr, const u8 *data, + u8 *aad, size_t *aad_len, u8 *nonce) +{ + u16 fc, stype, seq; + int qos = 0, addr4 = 0; + u8 *pos; + + nonce[0] = 0; + + fc = le_to_host16(hdr->frame_control); + stype = WLAN_FC_GET_STYPE(fc); + if ((fc & (WLAN_FC_TODS | WLAN_FC_FROMDS)) == + (WLAN_FC_TODS | WLAN_FC_FROMDS)) + addr4 = 1; + + if (WLAN_FC_GET_TYPE(fc) == WLAN_FC_TYPE_DATA) { + fc &= ~0x0070; /* Mask subtype bits */ + if (stype & 0x08) { + const u8 *qc; + qos = 1; + fc &= ~WLAN_FC_ORDER; + qc = (const u8 *) (hdr + 1); + if (addr4) + qc += ETH_ALEN; + nonce[0] = qc[0] & 0x0f; + } + } else if (WLAN_FC_GET_TYPE(fc) == WLAN_FC_TYPE_MGMT) + nonce[0] |= 0x10; /* Management */ + + fc &= ~(WLAN_FC_RETRY | WLAN_FC_PWRMGT | WLAN_FC_MOREDATA); + fc |= WLAN_FC_ISWEP; + WPA_PUT_LE16(aad, fc); + pos = aad + 2; + os_memcpy(pos, hdr->addr1, 3 * ETH_ALEN); + pos += 3 * ETH_ALEN; + seq = le_to_host16(hdr->seq_ctrl); + seq &= ~0xfff0; /* Mask Seq#; do not modify Frag# */ + WPA_PUT_LE16(pos, seq); + pos += 2; + + os_memcpy(pos, hdr + 1, addr4 * ETH_ALEN + qos * 2); + pos += addr4 * ETH_ALEN; + if (qos) { + pos[0] &= 0x70; + if (1 /* FIX: either device has SPP A-MSDU Capab = 0 */) + pos[0] &= 0x80; + pos++; + *pos++ = 0x00; + } + + *aad_len = pos - aad; + + os_memcpy(nonce + 1, hdr->addr2, ETH_ALEN); + nonce[7] = data[7]; /* PN5 */ + nonce[8] = data[6]; /* PN4 */ + nonce[9] = data[5]; /* PN3 */ + nonce[10] = data[4]; /* PN2 */ + nonce[11] = data[1]; /* PN1 */ + nonce[12] = data[0]; /* PN0 */ +} + + +static void xor_aes_block(u8 *dst, const u8 *src) +{ + u32 *d = (u32 *) dst; + u32 *s = (u32 *) src; + *d++ ^= *s++; + *d++ ^= *s++; + *d++ ^= *s++; + *d++ ^= *s++; +} + + +u8 * ccmp_decrypt(const u8 *tk, const struct ieee80211_hdr *hdr, + const u8 *data, size_t data_len, size_t *decrypted_len) +{ + u8 aad[2 + 30], nonce[13]; + size_t aad_len; + u8 b[AES_BLOCK_SIZE], x[AES_BLOCK_SIZE], a[AES_BLOCK_SIZE]; + void *aes; + const u8 *m, *mpos, *mic; + size_t mlen, last; + int i; + u8 *plain, *ppos; + u8 t[8]; + + if (data_len < 8 + 8) + return NULL; + + plain = os_malloc(data_len); + if (plain == NULL) + return NULL; + + aes = aes_encrypt_init(tk, 16); + if (aes == NULL) { + os_free(plain); + return NULL; + } + + m = data + 8; + mlen = data_len - 8 - 8; + last = mlen % AES_BLOCK_SIZE; + + os_memset(aad, 0, sizeof(aad)); + ccmp_aad_nonce(hdr, data, &aad[2], &aad_len, nonce); + WPA_PUT_BE16(aad, aad_len); + wpa_hexdump(MSG_EXCESSIVE, "CCMP AAD", &aad[2], aad_len); + wpa_hexdump(MSG_EXCESSIVE, "CCMP nonce", nonce, 13); + + /* CCM: M=8 L=2, Adata=1, M' = (M-2)/2 = 3, L' = L-1 = 1 */ + + /* A_i = Flags | Nonce N | Counter i */ + a[0] = 0x01; /* Flags = L' */ + os_memcpy(&a[1], nonce, 13); + + /* Decryption */ + + mic = data + data_len - 8; + wpa_hexdump(MSG_EXCESSIVE, "CCMP U", mic, 8); + /* U = T XOR S_0; S_0 = E(K, A_0) */ + WPA_PUT_BE16(&a[14], 0); + aes_encrypt(aes, a, x); + for (i = 0; i < 8; i++) + t[i] = mic[i] ^ x[i]; + wpa_hexdump(MSG_EXCESSIVE, "CCMP T", t, 8); + + /* plaintext = msg XOR (S_1 | S_2 | ... | S_n) */ + ppos = plain; + mpos = m; + for (i = 1; i <= mlen / AES_BLOCK_SIZE; i++) { + WPA_PUT_BE16(&a[14], i); + /* S_i = E(K, A_i) */ + aes_encrypt(aes, a, ppos); + xor_aes_block(ppos, mpos); + ppos += AES_BLOCK_SIZE; + mpos += AES_BLOCK_SIZE; + } + if (last) { + WPA_PUT_BE16(&a[14], i); + aes_encrypt(aes, a, ppos); + /* XOR zero-padded last block */ + for (i = 0; i < last; i++) + *ppos++ ^= *mpos++; + } + wpa_hexdump(MSG_EXCESSIVE, "CCMP decrypted", plain, mlen); + + /* Authentication */ + /* B_0: Flags | Nonce N | l(m) */ + b[0] = 0x40 /* Adata */ | (3 /* M' */ << 3) | 1 /* L' */; + os_memcpy(&b[1], nonce, 13); + WPA_PUT_BE16(&b[14], mlen); + + wpa_hexdump(MSG_EXCESSIVE, "CCMP B_0", b, AES_BLOCK_SIZE); + aes_encrypt(aes, b, x); /* X_1 = E(K, B_0) */ + + wpa_hexdump(MSG_EXCESSIVE, "CCMP B_1", aad, AES_BLOCK_SIZE); + xor_aes_block(aad, x); + aes_encrypt(aes, aad, x); /* X_2 = E(K, X_1 XOR B_1) */ + + wpa_hexdump(MSG_EXCESSIVE, "CCMP B_2", &aad[AES_BLOCK_SIZE], + AES_BLOCK_SIZE); + xor_aes_block(&aad[AES_BLOCK_SIZE], x); + aes_encrypt(aes, &aad[AES_BLOCK_SIZE], x); /* X_3 = E(K, X_2 XOR B_2) + */ + + ppos = plain; + for (i = 0; i < mlen / AES_BLOCK_SIZE; i++) { + /* X_i+1 = E(K, X_i XOR B_i) */ + xor_aes_block(x, ppos); + ppos += AES_BLOCK_SIZE; + aes_encrypt(aes, x, x); + } + if (last) { + /* XOR zero-padded last block */ + for (i = 0; i < last; i++) + x[i] ^= *ppos++; + aes_encrypt(aes, x, x); + } + + aes_encrypt_deinit(aes); + + if (os_memcmp(x, t, 8) != 0) { + wpa_printf(MSG_INFO, "Invalid CCMP MIC in frame from " MACSTR, + MAC2STR(hdr->addr2)); + os_free(plain); + return NULL; + } + + *decrypted_len = mlen; + return plain; +} diff --git a/wlantest/rx_data.c b/wlantest/rx_data.c index ac24c252d..34679eddf 100644 --- a/wlantest/rx_data.c +++ b/wlantest/rx_data.c @@ -691,13 +691,86 @@ static void rx_data_process(struct wlantest *wt, const u8 *dst, const u8 *src, } +static void rx_data_bss_prot_group(struct wlantest *wt, + const struct ieee80211_hdr *hdr, + const u8 *qos, const u8 *dst, const u8 *src, + const u8 *data, size_t len) +{ + struct wlantest_bss *bss; + int keyid; + + bss = bss_get(wt, hdr->addr2); + if (bss == NULL) + return; + if (len < 4) { + wpa_printf(MSG_INFO, "Too short group addressed data frame"); + return; + } + + keyid = data[3] >> 6; + if (bss->gtk_len[keyid] == 0) { + wpa_printf(MSG_MSGDUMP, "No GTK known to decrypt the frame " + "(A2=" MACSTR " KeyID=%d)", + MAC2STR(hdr->addr2), keyid); + return; + } + + /* TODO: try to decrypt */ +} + + static void rx_data_bss_prot(struct wlantest *wt, const struct ieee80211_hdr *hdr, const u8 *qos, const u8 *dst, const u8 *src, const u8 *data, size_t len) { - /* TODO: Try to decrypt and if success, call rx_data_process() with - * prot = 1 */ + struct wlantest_bss *bss; + struct wlantest_sta *sta; + int keyid; + u16 fc = le_to_host16(hdr->frame_control); + u8 *decrypted; + size_t dlen; + + if (hdr->addr1[0] & 0x01) { + rx_data_bss_prot_group(wt, hdr, qos, dst, src, data, len); + return; + } + + if (fc & WLAN_FC_TODS) { + bss = bss_get(wt, hdr->addr1); + if (bss == NULL) + return; + sta = sta_get(bss, hdr->addr2); + } else { + bss = bss_get(wt, hdr->addr2); + if (bss == NULL) + return; + sta = sta_get(bss, hdr->addr1); + } + if (sta == NULL || !sta->ptk_set) { + wpa_printf(MSG_MSGDUMP, "No PTK known to decrypt the frame"); + return; + } + + if (len < 4) { + wpa_printf(MSG_INFO, "Too short encrypted data frame"); + return; + } + + keyid = data[3] >> 6; + if (keyid != 0) { + wpa_printf(MSG_INFO, "Unexpected non-zero KeyID %d in " + "individually addressed Data frame from " MACSTR, + keyid, MAC2STR(hdr->addr2)); + } + + /* TODO: check PN for replay */ + /* TODO: TKIP */ + + decrypted = ccmp_decrypt(sta->ptk.tk1, hdr, data, len, &dlen); + if (decrypted) + rx_data_process(wt, dst, src, decrypted, dlen, 1); + os_free(decrypted); } diff --git a/wlantest/wlantest.h b/wlantest/wlantest.h index 443559a72..a1fd39260 100644 --- a/wlantest/wlantest.h +++ b/wlantest/wlantest.h @@ -20,6 +20,7 @@ struct ieee802_11_elems; struct radius_msg; +struct ieee80211_hdr; #define MAX_RADIUS_SECRET_LEN 128 @@ -121,4 +122,7 @@ void sta_deinit(struct wlantest_sta *sta); void sta_update_assoc(struct wlantest_sta *sta, struct ieee802_11_elems *elems); +u8 * ccmp_decrypt(const u8 *tk, const struct ieee80211_hdr *hdr, + const u8 *data, size_t data_len, size_t *decrypted_len); + #endif /* WLANTEST_H */