f868d5607d
The AMPE element includes number of optional and variable length fields and those cannot really be represented by a fixed struct ieee80211_ampe_ie. Remove the optional fields from the struct and build/parse these fields separately. This is also adding support for IGTKdata that was completely missing from the previous implementation. In addition, Key RSC for MGTK is now filled in and used when configuring the RX MGTK for a peer. Signed-off-by: Jouni Malinen <j@w1.fi>
730 lines
18 KiB
C
730 lines
18 KiB
C
/*
|
|
* WPA Supplicant - Mesh RSN routines
|
|
* Copyright (c) 2013-2014, cozybit, Inc. All rights reserved.
|
|
*
|
|
* This software may be distributed under the terms of the BSD license.
|
|
* See README for more details.
|
|
*/
|
|
|
|
#include "utils/includes.h"
|
|
|
|
#include "utils/common.h"
|
|
#include "utils/eloop.h"
|
|
#include "crypto/sha256.h"
|
|
#include "crypto/random.h"
|
|
#include "crypto/aes.h"
|
|
#include "crypto/aes_siv.h"
|
|
#include "rsn_supp/wpa.h"
|
|
#include "ap/hostapd.h"
|
|
#include "ap/wpa_auth.h"
|
|
#include "ap/sta_info.h"
|
|
#include "ap/ieee802_11.h"
|
|
#include "wpa_supplicant_i.h"
|
|
#include "driver_i.h"
|
|
#include "wpas_glue.h"
|
|
#include "mesh_mpm.h"
|
|
#include "mesh_rsn.h"
|
|
|
|
#define MESH_AUTH_TIMEOUT 10
|
|
#define MESH_AUTH_RETRY 3
|
|
|
|
void mesh_auth_timer(void *eloop_ctx, void *user_data)
|
|
{
|
|
struct wpa_supplicant *wpa_s = eloop_ctx;
|
|
struct sta_info *sta = user_data;
|
|
struct hostapd_data *hapd;
|
|
|
|
if (sta->sae->state != SAE_ACCEPTED) {
|
|
wpa_printf(MSG_DEBUG, "AUTH: Re-authenticate with " MACSTR
|
|
" (attempt %d) ",
|
|
MAC2STR(sta->addr), sta->sae_auth_retry);
|
|
wpa_msg(wpa_s, MSG_INFO, MESH_SAE_AUTH_FAILURE "addr=" MACSTR,
|
|
MAC2STR(sta->addr));
|
|
if (sta->sae_auth_retry < MESH_AUTH_RETRY) {
|
|
mesh_rsn_auth_sae_sta(wpa_s, sta);
|
|
} else {
|
|
hapd = wpa_s->ifmsh->bss[0];
|
|
|
|
if (sta->sae_auth_retry > MESH_AUTH_RETRY) {
|
|
ap_free_sta(hapd, sta);
|
|
return;
|
|
}
|
|
|
|
/* block the STA if exceeded the number of attempts */
|
|
wpa_mesh_set_plink_state(wpa_s, sta, PLINK_BLOCKED);
|
|
sta->sae->state = SAE_NOTHING;
|
|
wpa_msg(wpa_s, MSG_INFO, MESH_SAE_AUTH_BLOCKED "addr="
|
|
MACSTR " duration=%d",
|
|
MAC2STR(sta->addr),
|
|
hapd->conf->ap_max_inactivity);
|
|
}
|
|
sta->sae_auth_retry++;
|
|
}
|
|
}
|
|
|
|
|
|
static void auth_logger(void *ctx, const u8 *addr, logger_level level,
|
|
const char *txt)
|
|
{
|
|
if (addr)
|
|
wpa_printf(MSG_DEBUG, "AUTH: " MACSTR " - %s",
|
|
MAC2STR(addr), txt);
|
|
else
|
|
wpa_printf(MSG_DEBUG, "AUTH: %s", txt);
|
|
}
|
|
|
|
|
|
static const u8 *auth_get_psk(void *ctx, const u8 *addr,
|
|
const u8 *p2p_dev_addr, const u8 *prev_psk)
|
|
{
|
|
struct mesh_rsn *mesh_rsn = ctx;
|
|
struct hostapd_data *hapd = mesh_rsn->wpa_s->ifmsh->bss[0];
|
|
struct sta_info *sta = ap_get_sta(hapd, addr);
|
|
|
|
wpa_printf(MSG_DEBUG, "AUTH: %s (addr=" MACSTR " prev_psk=%p)",
|
|
__func__, MAC2STR(addr), prev_psk);
|
|
|
|
if (sta && sta->auth_alg == WLAN_AUTH_SAE) {
|
|
if (!sta->sae || prev_psk)
|
|
return NULL;
|
|
return sta->sae->pmk;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
static int auth_set_key(void *ctx, int vlan_id, enum wpa_alg alg,
|
|
const u8 *addr, int idx, u8 *key, size_t key_len)
|
|
{
|
|
struct mesh_rsn *mesh_rsn = ctx;
|
|
u8 seq[6];
|
|
|
|
os_memset(seq, 0, sizeof(seq));
|
|
|
|
if (addr) {
|
|
wpa_printf(MSG_DEBUG, "AUTH: %s(alg=%d addr=" MACSTR
|
|
" key_idx=%d)",
|
|
__func__, alg, MAC2STR(addr), idx);
|
|
} else {
|
|
wpa_printf(MSG_DEBUG, "AUTH: %s(alg=%d key_idx=%d)",
|
|
__func__, alg, idx);
|
|
}
|
|
wpa_hexdump_key(MSG_DEBUG, "AUTH: set_key - key", key, key_len);
|
|
|
|
return wpa_drv_set_key(mesh_rsn->wpa_s, alg, addr, idx,
|
|
1, seq, 6, key, key_len);
|
|
}
|
|
|
|
|
|
static int auth_start_ampe(void *ctx, const u8 *addr)
|
|
{
|
|
struct mesh_rsn *mesh_rsn = ctx;
|
|
struct hostapd_data *hapd;
|
|
struct sta_info *sta;
|
|
|
|
if (mesh_rsn->wpa_s->current_ssid->mode != WPAS_MODE_MESH)
|
|
return -1;
|
|
|
|
hapd = mesh_rsn->wpa_s->ifmsh->bss[0];
|
|
sta = ap_get_sta(hapd, addr);
|
|
if (sta)
|
|
eloop_cancel_timeout(mesh_auth_timer, mesh_rsn->wpa_s, sta);
|
|
|
|
mesh_mpm_auth_peer(mesh_rsn->wpa_s, addr);
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int __mesh_rsn_auth_init(struct mesh_rsn *rsn, const u8 *addr,
|
|
enum mfp_options ieee80211w)
|
|
{
|
|
struct wpa_auth_config conf;
|
|
struct wpa_auth_callbacks cb;
|
|
u8 seq[6] = {};
|
|
|
|
wpa_printf(MSG_DEBUG, "AUTH: Initializing group state machine");
|
|
|
|
os_memset(&conf, 0, sizeof(conf));
|
|
conf.wpa = 2;
|
|
conf.wpa_key_mgmt = WPA_KEY_MGMT_SAE;
|
|
conf.wpa_pairwise = WPA_CIPHER_CCMP;
|
|
conf.rsn_pairwise = WPA_CIPHER_CCMP;
|
|
conf.wpa_group = WPA_CIPHER_CCMP;
|
|
conf.eapol_version = 0;
|
|
conf.wpa_group_rekey = -1;
|
|
#ifdef CONFIG_IEEE80211W
|
|
conf.ieee80211w = ieee80211w;
|
|
if (ieee80211w != NO_MGMT_FRAME_PROTECTION)
|
|
conf.group_mgmt_cipher = WPA_CIPHER_AES_128_CMAC;
|
|
#endif /* CONFIG_IEEE80211W */
|
|
|
|
os_memset(&cb, 0, sizeof(cb));
|
|
cb.ctx = rsn;
|
|
cb.logger = auth_logger;
|
|
cb.get_psk = auth_get_psk;
|
|
cb.set_key = auth_set_key;
|
|
cb.start_ampe = auth_start_ampe;
|
|
|
|
rsn->auth = wpa_init(addr, &conf, &cb);
|
|
if (rsn->auth == NULL) {
|
|
wpa_printf(MSG_DEBUG, "AUTH: wpa_init() failed");
|
|
return -1;
|
|
}
|
|
|
|
/* TODO: support rekeying */
|
|
rsn->mgtk_len = wpa_cipher_key_len(WPA_CIPHER_CCMP);
|
|
if (random_get_bytes(rsn->mgtk, rsn->mgtk_len) < 0)
|
|
return -1;
|
|
rsn->mgtk_key_id = 1;
|
|
|
|
#ifdef CONFIG_IEEE80211W
|
|
if (ieee80211w != NO_MGMT_FRAME_PROTECTION) {
|
|
rsn->igtk_len = wpa_cipher_key_len(WPA_CIPHER_AES_128_CMAC);
|
|
if (random_get_bytes(rsn->igtk, rsn->igtk_len) < 0)
|
|
return -1;
|
|
rsn->igtk_key_id = 4;
|
|
|
|
/* group mgmt */
|
|
wpa_hexdump_key(MSG_DEBUG, "mesh: Own TX IGTK",
|
|
rsn->igtk, rsn->igtk_len);
|
|
wpa_drv_set_key(rsn->wpa_s, WPA_ALG_IGTK, NULL,
|
|
rsn->igtk_key_id, 1,
|
|
seq, sizeof(seq), rsn->igtk, rsn->igtk_len);
|
|
}
|
|
#endif /* CONFIG_IEEE80211W */
|
|
|
|
/* group privacy / data frames */
|
|
wpa_hexdump_key(MSG_DEBUG, "mesh: Own TX MGTK",
|
|
rsn->mgtk, rsn->mgtk_len);
|
|
wpa_drv_set_key(rsn->wpa_s, WPA_ALG_CCMP, NULL, rsn->mgtk_key_id, 1,
|
|
seq, sizeof(seq), rsn->mgtk, rsn->mgtk_len);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static void mesh_rsn_deinit(struct mesh_rsn *rsn)
|
|
{
|
|
os_memset(rsn->mgtk, 0, sizeof(rsn->mgtk));
|
|
rsn->mgtk_len = 0;
|
|
os_memset(rsn->igtk, 0, sizeof(rsn->igtk));
|
|
rsn->igtk_len = 0;
|
|
if (rsn->auth)
|
|
wpa_deinit(rsn->auth);
|
|
}
|
|
|
|
|
|
struct mesh_rsn *mesh_rsn_auth_init(struct wpa_supplicant *wpa_s,
|
|
struct mesh_conf *conf)
|
|
{
|
|
struct mesh_rsn *mesh_rsn;
|
|
struct hostapd_data *bss = wpa_s->ifmsh->bss[0];
|
|
const u8 *ie;
|
|
size_t ie_len;
|
|
|
|
mesh_rsn = os_zalloc(sizeof(*mesh_rsn));
|
|
if (mesh_rsn == NULL)
|
|
return NULL;
|
|
mesh_rsn->wpa_s = wpa_s;
|
|
|
|
if (__mesh_rsn_auth_init(mesh_rsn, wpa_s->own_addr,
|
|
conf->ieee80211w) < 0) {
|
|
mesh_rsn_deinit(mesh_rsn);
|
|
os_free(mesh_rsn);
|
|
return NULL;
|
|
}
|
|
|
|
bss->wpa_auth = mesh_rsn->auth;
|
|
|
|
ie = wpa_auth_get_wpa_ie(mesh_rsn->auth, &ie_len);
|
|
conf->rsn_ie = (u8 *) ie;
|
|
conf->rsn_ie_len = ie_len;
|
|
|
|
wpa_supplicant_rsn_supp_set_config(wpa_s, wpa_s->current_ssid);
|
|
|
|
return mesh_rsn;
|
|
}
|
|
|
|
|
|
static int index_within_array(const int *array, int idx)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < idx; i++) {
|
|
if (array[i] == -1)
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
static int mesh_rsn_sae_group(struct wpa_supplicant *wpa_s,
|
|
struct sae_data *sae)
|
|
{
|
|
int *groups = wpa_s->ifmsh->bss[0]->conf->sae_groups;
|
|
|
|
/* Configuration may have changed, so validate current index */
|
|
if (!index_within_array(groups, wpa_s->mesh_rsn->sae_group_index))
|
|
return -1;
|
|
|
|
for (;;) {
|
|
int group = groups[wpa_s->mesh_rsn->sae_group_index];
|
|
|
|
if (group <= 0)
|
|
break;
|
|
if (sae_set_group(sae, group) == 0) {
|
|
wpa_dbg(wpa_s, MSG_DEBUG, "SME: Selected SAE group %d",
|
|
sae->group);
|
|
return 0;
|
|
}
|
|
wpa_s->mesh_rsn->sae_group_index++;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
|
|
static int mesh_rsn_build_sae_commit(struct wpa_supplicant *wpa_s,
|
|
struct wpa_ssid *ssid,
|
|
struct sta_info *sta)
|
|
{
|
|
if (ssid->passphrase == NULL) {
|
|
wpa_msg(wpa_s, MSG_DEBUG, "SAE: No password available");
|
|
return -1;
|
|
}
|
|
|
|
if (mesh_rsn_sae_group(wpa_s, sta->sae) < 0) {
|
|
wpa_msg(wpa_s, MSG_DEBUG, "SAE: Failed to select group");
|
|
return -1;
|
|
}
|
|
|
|
return sae_prepare_commit(wpa_s->own_addr, sta->addr,
|
|
(u8 *) ssid->passphrase,
|
|
os_strlen(ssid->passphrase), sta->sae);
|
|
}
|
|
|
|
|
|
/* initiate new SAE authentication with sta */
|
|
int mesh_rsn_auth_sae_sta(struct wpa_supplicant *wpa_s,
|
|
struct sta_info *sta)
|
|
{
|
|
struct hostapd_data *hapd = wpa_s->ifmsh->bss[0];
|
|
struct wpa_ssid *ssid = wpa_s->current_ssid;
|
|
struct rsn_pmksa_cache_entry *pmksa;
|
|
unsigned int rnd;
|
|
int ret;
|
|
|
|
if (!ssid) {
|
|
wpa_msg(wpa_s, MSG_DEBUG,
|
|
"AUTH: No current_ssid known to initiate new SAE");
|
|
return -1;
|
|
}
|
|
|
|
if (!sta->sae) {
|
|
sta->sae = os_zalloc(sizeof(*sta->sae));
|
|
if (sta->sae == NULL)
|
|
return -1;
|
|
}
|
|
|
|
pmksa = wpa_auth_pmksa_get(hapd->wpa_auth, sta->addr);
|
|
if (pmksa) {
|
|
if (!sta->wpa_sm)
|
|
sta->wpa_sm = wpa_auth_sta_init(hapd->wpa_auth,
|
|
sta->addr, NULL);
|
|
if (!sta->wpa_sm) {
|
|
wpa_printf(MSG_ERROR,
|
|
"mesh: Failed to initialize RSN state machine");
|
|
return -1;
|
|
}
|
|
|
|
wpa_printf(MSG_DEBUG,
|
|
"AUTH: Mesh PMKSA cache entry found for " MACSTR
|
|
" - try to use PMKSA caching instead of new SAE authentication",
|
|
MAC2STR(sta->addr));
|
|
wpa_auth_pmksa_set_to_sm(pmksa, sta->wpa_sm, hapd->wpa_auth,
|
|
sta->sae->pmkid, sta->sae->pmk);
|
|
sae_accept_sta(hapd, sta);
|
|
sta->mesh_sae_pmksa_caching = 1;
|
|
return 0;
|
|
}
|
|
sta->mesh_sae_pmksa_caching = 0;
|
|
|
|
if (mesh_rsn_build_sae_commit(wpa_s, ssid, sta))
|
|
return -1;
|
|
|
|
wpa_msg(wpa_s, MSG_DEBUG,
|
|
"AUTH: started authentication with SAE peer: " MACSTR,
|
|
MAC2STR(sta->addr));
|
|
|
|
wpa_supplicant_set_state(wpa_s, WPA_AUTHENTICATING);
|
|
ret = auth_sae_init_committed(hapd, sta);
|
|
if (ret)
|
|
return ret;
|
|
|
|
eloop_cancel_timeout(mesh_auth_timer, wpa_s, sta);
|
|
rnd = rand() % MESH_AUTH_TIMEOUT;
|
|
eloop_register_timeout(MESH_AUTH_TIMEOUT + rnd, 0, mesh_auth_timer,
|
|
wpa_s, sta);
|
|
return 0;
|
|
}
|
|
|
|
|
|
void mesh_rsn_get_pmkid(struct mesh_rsn *rsn, struct sta_info *sta, u8 *pmkid)
|
|
{
|
|
os_memcpy(pmkid, sta->sae->pmkid, SAE_PMKID_LEN);
|
|
}
|
|
|
|
|
|
static void
|
|
mesh_rsn_derive_aek(struct mesh_rsn *rsn, struct sta_info *sta)
|
|
{
|
|
u8 *myaddr = rsn->wpa_s->own_addr;
|
|
u8 *peer = sta->addr;
|
|
u8 *addr1, *addr2;
|
|
u8 context[RSN_SELECTOR_LEN + 2 * ETH_ALEN], *ptr = context;
|
|
|
|
/*
|
|
* AEK = KDF-Hash-256(PMK, "AEK Derivation", Selected AKM Suite ||
|
|
* min(localMAC, peerMAC) || max(localMAC, peerMAC))
|
|
*/
|
|
/* Selected AKM Suite: SAE */
|
|
RSN_SELECTOR_PUT(ptr, RSN_AUTH_KEY_MGMT_SAE);
|
|
ptr += RSN_SELECTOR_LEN;
|
|
|
|
if (os_memcmp(myaddr, peer, ETH_ALEN) < 0) {
|
|
addr1 = myaddr;
|
|
addr2 = peer;
|
|
} else {
|
|
addr1 = peer;
|
|
addr2 = myaddr;
|
|
}
|
|
os_memcpy(ptr, addr1, ETH_ALEN);
|
|
ptr += ETH_ALEN;
|
|
os_memcpy(ptr, addr2, ETH_ALEN);
|
|
|
|
sha256_prf(sta->sae->pmk, sizeof(sta->sae->pmk), "AEK Derivation",
|
|
context, sizeof(context), sta->aek, sizeof(sta->aek));
|
|
}
|
|
|
|
|
|
/* derive mesh temporal key from pmk */
|
|
int mesh_rsn_derive_mtk(struct wpa_supplicant *wpa_s, struct sta_info *sta)
|
|
{
|
|
u8 *ptr;
|
|
u8 *min, *max;
|
|
u8 *myaddr = wpa_s->own_addr;
|
|
u8 *peer = sta->addr;
|
|
u8 context[2 * WPA_NONCE_LEN + 2 * 2 + RSN_SELECTOR_LEN + 2 * ETH_ALEN];
|
|
|
|
/*
|
|
* MTK = KDF-Hash-Length(PMK, "Temporal Key Derivation", min(localNonce,
|
|
* peerNonce) || max(localNonce, peerNonce) || min(localLinkID,
|
|
* peerLinkID) || max(localLinkID, peerLinkID) || Selected AKM Suite ||
|
|
* min(localMAC, peerMAC) || max(localMAC, peerMAC))
|
|
*/
|
|
ptr = context;
|
|
if (os_memcmp(sta->my_nonce, sta->peer_nonce, WPA_NONCE_LEN) < 0) {
|
|
min = sta->my_nonce;
|
|
max = sta->peer_nonce;
|
|
} else {
|
|
min = sta->peer_nonce;
|
|
max = sta->my_nonce;
|
|
}
|
|
os_memcpy(ptr, min, WPA_NONCE_LEN);
|
|
ptr += WPA_NONCE_LEN;
|
|
os_memcpy(ptr, max, WPA_NONCE_LEN);
|
|
ptr += WPA_NONCE_LEN;
|
|
|
|
if (sta->my_lid < sta->peer_lid) {
|
|
WPA_PUT_LE16(ptr, sta->my_lid);
|
|
ptr += 2;
|
|
WPA_PUT_LE16(ptr, sta->peer_lid);
|
|
ptr += 2;
|
|
} else {
|
|
WPA_PUT_LE16(ptr, sta->peer_lid);
|
|
ptr += 2;
|
|
WPA_PUT_LE16(ptr, sta->my_lid);
|
|
ptr += 2;
|
|
}
|
|
|
|
/* Selected AKM Suite: SAE */
|
|
RSN_SELECTOR_PUT(ptr, RSN_AUTH_KEY_MGMT_SAE);
|
|
ptr += RSN_SELECTOR_LEN;
|
|
|
|
if (os_memcmp(myaddr, peer, ETH_ALEN) < 0) {
|
|
min = myaddr;
|
|
max = peer;
|
|
} else {
|
|
min = peer;
|
|
max = myaddr;
|
|
}
|
|
os_memcpy(ptr, min, ETH_ALEN);
|
|
ptr += ETH_ALEN;
|
|
os_memcpy(ptr, max, ETH_ALEN);
|
|
|
|
sta->mtk_len = wpa_cipher_key_len(WPA_CIPHER_CCMP);
|
|
sha256_prf(sta->sae->pmk, SAE_PMK_LEN,
|
|
"Temporal Key Derivation", context, sizeof(context),
|
|
sta->mtk, sta->mtk_len);
|
|
return 0;
|
|
}
|
|
|
|
|
|
void mesh_rsn_init_ampe_sta(struct wpa_supplicant *wpa_s, struct sta_info *sta)
|
|
{
|
|
if (random_get_bytes(sta->my_nonce, WPA_NONCE_LEN) < 0) {
|
|
wpa_printf(MSG_INFO, "mesh: Failed to derive random nonce");
|
|
/* TODO: How to handle this more cleanly? */
|
|
}
|
|
os_memset(sta->peer_nonce, 0, WPA_NONCE_LEN);
|
|
mesh_rsn_derive_aek(wpa_s->mesh_rsn, sta);
|
|
}
|
|
|
|
|
|
/* insert AMPE and encrypted MIC at @ie.
|
|
* @mesh_rsn: mesh RSN context
|
|
* @sta: STA we're sending to
|
|
* @cat: pointer to category code in frame header.
|
|
* @buf: wpabuf to add encrypted AMPE and MIC to.
|
|
* */
|
|
int mesh_rsn_protect_frame(struct mesh_rsn *rsn, struct sta_info *sta,
|
|
const u8 *cat, struct wpabuf *buf)
|
|
{
|
|
struct ieee80211_ampe_ie *ampe;
|
|
u8 const *ie = wpabuf_head_u8(buf) + wpabuf_len(buf);
|
|
u8 *ampe_ie, *pos, *mic_payload;
|
|
const u8 *aad[] = { rsn->wpa_s->own_addr, sta->addr, cat };
|
|
const size_t aad_len[] = { ETH_ALEN, ETH_ALEN, ie - cat };
|
|
int ret = 0;
|
|
size_t len;
|
|
|
|
len = sizeof(*ampe) + rsn->mgtk_len + WPA_KEY_RSC_LEN + 4;
|
|
#ifdef CONFIG_IEEE80211W
|
|
if (rsn->igtk_len)
|
|
len += 2 + 6 + rsn->igtk_len;
|
|
#endif /* CONFIG_IEEE80211W */
|
|
|
|
if (2 + AES_BLOCK_SIZE + 2 + len > wpabuf_tailroom(buf)) {
|
|
wpa_printf(MSG_ERROR, "protect frame: buffer too small");
|
|
return -EINVAL;
|
|
}
|
|
|
|
ampe_ie = os_zalloc(2 + len);
|
|
if (!ampe_ie) {
|
|
wpa_printf(MSG_ERROR, "protect frame: out of memory");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/* IE: AMPE */
|
|
ampe_ie[0] = WLAN_EID_AMPE;
|
|
ampe_ie[1] = len;
|
|
ampe = (struct ieee80211_ampe_ie *) (ampe_ie + 2);
|
|
|
|
RSN_SELECTOR_PUT(ampe->selected_pairwise_suite,
|
|
RSN_CIPHER_SUITE_CCMP);
|
|
os_memcpy(ampe->local_nonce, sta->my_nonce, WPA_NONCE_LEN);
|
|
os_memcpy(ampe->peer_nonce, sta->peer_nonce, WPA_NONCE_LEN);
|
|
|
|
pos = (u8 *) (ampe + 1);
|
|
|
|
/* TODO: Key Replay Counter[8] optionally for
|
|
* Mesh Group Key Inform/Acknowledge frames */
|
|
|
|
/* TODO: static mgtk for now since we don't support rekeying! */
|
|
/*
|
|
* GTKdata[variable]:
|
|
* MGTK[variable] || Key RSC[8] || GTKExpirationTime[4]
|
|
*/
|
|
os_memcpy(pos, rsn->mgtk, rsn->mgtk_len);
|
|
pos += rsn->mgtk_len;
|
|
wpa_drv_get_seqnum(rsn->wpa_s, NULL, rsn->mgtk_key_id, pos);
|
|
pos += WPA_KEY_RSC_LEN;
|
|
/* Use fixed GTKExpirationTime for now */
|
|
WPA_PUT_LE32(pos, 0xffffffff);
|
|
pos += 4;
|
|
|
|
#ifdef CONFIG_IEEE80211W
|
|
/*
|
|
* IGTKdata[variable]:
|
|
* Key ID[2], IPN[6], IGTK[variable]
|
|
*/
|
|
if (rsn->igtk_len) {
|
|
WPA_PUT_LE16(pos, rsn->igtk_key_id);
|
|
pos += 2;
|
|
wpa_drv_get_seqnum(rsn->wpa_s, NULL, rsn->igtk_key_id, pos);
|
|
pos += 6;
|
|
os_memcpy(pos, rsn->igtk, rsn->igtk_len);
|
|
}
|
|
#endif /* CONFIG_IEEE80211W */
|
|
|
|
wpa_hexdump_key(MSG_DEBUG, "mesh: Plaintext AMPE element",
|
|
ampe_ie, 2 + len);
|
|
|
|
/* IE: MIC */
|
|
wpabuf_put_u8(buf, WLAN_EID_MIC);
|
|
wpabuf_put_u8(buf, AES_BLOCK_SIZE);
|
|
/* MIC field is output ciphertext */
|
|
|
|
/* encrypt after MIC */
|
|
mic_payload = wpabuf_put(buf, 2 + len + AES_BLOCK_SIZE);
|
|
|
|
if (aes_siv_encrypt(sta->aek, ampe_ie, 2 + len, 3,
|
|
aad, aad_len, mic_payload)) {
|
|
wpa_printf(MSG_ERROR, "protect frame: failed to encrypt");
|
|
ret = -ENOMEM;
|
|
}
|
|
|
|
os_free(ampe_ie);
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
int mesh_rsn_process_ampe(struct wpa_supplicant *wpa_s, struct sta_info *sta,
|
|
struct ieee802_11_elems *elems, const u8 *cat,
|
|
const u8 *chosen_pmk,
|
|
const u8 *start, size_t elems_len)
|
|
{
|
|
int ret = 0;
|
|
struct ieee80211_ampe_ie *ampe;
|
|
u8 null_nonce[WPA_NONCE_LEN] = {};
|
|
u8 ampe_eid;
|
|
u8 ampe_ie_len;
|
|
u8 *ampe_buf, *crypt = NULL, *pos, *end;
|
|
size_t crypt_len;
|
|
const u8 *aad[] = { sta->addr, wpa_s->own_addr, cat };
|
|
const size_t aad_len[] = { ETH_ALEN, ETH_ALEN,
|
|
(elems->mic - 2) - cat };
|
|
size_t key_len;
|
|
|
|
if (!sta->sae) {
|
|
struct hostapd_data *hapd = wpa_s->ifmsh->bss[0];
|
|
|
|
if (!wpa_auth_pmksa_get(hapd->wpa_auth, sta->addr)) {
|
|
wpa_printf(MSG_INFO,
|
|
"Mesh RSN: SAE is not prepared yet");
|
|
return -1;
|
|
}
|
|
mesh_rsn_auth_sae_sta(wpa_s, sta);
|
|
}
|
|
|
|
if (chosen_pmk && os_memcmp(chosen_pmk, sta->sae->pmkid, PMKID_LEN)) {
|
|
wpa_msg(wpa_s, MSG_DEBUG,
|
|
"Mesh RSN: Invalid PMKID (Chosen PMK did not match calculated PMKID)");
|
|
return -1;
|
|
}
|
|
|
|
if (!elems->mic || elems->mic_len < AES_BLOCK_SIZE) {
|
|
wpa_msg(wpa_s, MSG_DEBUG, "Mesh RSN: missing mic ie");
|
|
return -1;
|
|
}
|
|
|
|
ampe_buf = (u8 *) elems->mic + elems->mic_len;
|
|
if ((int) elems_len < ampe_buf - start)
|
|
return -1;
|
|
|
|
crypt_len = elems_len - (elems->mic - start);
|
|
if (crypt_len < 2 + AES_BLOCK_SIZE) {
|
|
wpa_msg(wpa_s, MSG_DEBUG, "Mesh RSN: missing ampe ie");
|
|
return -1;
|
|
}
|
|
|
|
/* crypt is modified by siv_decrypt */
|
|
crypt = os_zalloc(crypt_len);
|
|
if (!crypt) {
|
|
wpa_printf(MSG_ERROR, "Mesh RSN: out of memory");
|
|
ret = -ENOMEM;
|
|
goto free;
|
|
}
|
|
|
|
os_memcpy(crypt, elems->mic, crypt_len);
|
|
|
|
if (aes_siv_decrypt(sta->aek, crypt, crypt_len, 3,
|
|
aad, aad_len, ampe_buf)) {
|
|
wpa_printf(MSG_ERROR, "Mesh RSN: frame verification failed!");
|
|
ret = -1;
|
|
goto free;
|
|
}
|
|
|
|
crypt_len -= AES_BLOCK_SIZE;
|
|
wpa_hexdump_key(MSG_DEBUG, "mesh: Decrypted AMPE element",
|
|
ampe_buf, crypt_len);
|
|
|
|
ampe_eid = *ampe_buf++;
|
|
ampe_ie_len = *ampe_buf++;
|
|
|
|
if (ampe_eid != WLAN_EID_AMPE ||
|
|
(size_t) 2 + ampe_ie_len > crypt_len ||
|
|
ampe_ie_len < sizeof(struct ieee80211_ampe_ie)) {
|
|
wpa_msg(wpa_s, MSG_DEBUG, "Mesh RSN: invalid ampe ie");
|
|
ret = -1;
|
|
goto free;
|
|
}
|
|
|
|
ampe = (struct ieee80211_ampe_ie *) ampe_buf;
|
|
pos = (u8 *) (ampe + 1);
|
|
end = ampe_buf + ampe_ie_len;
|
|
if (os_memcmp(ampe->peer_nonce, null_nonce, WPA_NONCE_LEN) != 0 &&
|
|
os_memcmp(ampe->peer_nonce, sta->my_nonce, WPA_NONCE_LEN) != 0) {
|
|
wpa_msg(wpa_s, MSG_DEBUG, "Mesh RSN: invalid peer nonce");
|
|
ret = -1;
|
|
goto free;
|
|
}
|
|
os_memcpy(sta->peer_nonce, ampe->local_nonce,
|
|
sizeof(ampe->local_nonce));
|
|
|
|
/* TODO: Key Replay Counter[8] in Mesh Group Key Inform/Acknowledge
|
|
* frames */
|
|
|
|
/*
|
|
* GTKdata[variable]:
|
|
* MGTK[variable] || Key RSC[8] || GTKExpirationTime[4]
|
|
*/
|
|
key_len = wpa_cipher_key_len(WPA_CIPHER_CCMP);
|
|
if ((int) key_len + WPA_KEY_RSC_LEN + 4 > end - pos) {
|
|
wpa_dbg(wpa_s, MSG_DEBUG, "mesh: Truncated AMPE element");
|
|
ret = -1;
|
|
goto free;
|
|
}
|
|
sta->mgtk_len = key_len;
|
|
os_memcpy(sta->mgtk, pos, sta->mgtk_len);
|
|
wpa_hexdump_key(MSG_DEBUG, "mesh: GTKdata - MGTK",
|
|
sta->mgtk, sta->mgtk_len);
|
|
pos += sta->mgtk_len;
|
|
wpa_hexdump(MSG_DEBUG, "mesh: GTKdata - MGTK - Key RSC",
|
|
pos, WPA_KEY_RSC_LEN);
|
|
os_memcpy(sta->mgtk_rsc, pos, sizeof(sta->mgtk_rsc));
|
|
pos += WPA_KEY_RSC_LEN;
|
|
wpa_printf(MSG_DEBUG,
|
|
"mesh: GTKdata - MGTK - GTKExpirationTime: %u seconds",
|
|
WPA_GET_LE32(pos));
|
|
pos += 4;
|
|
|
|
#ifdef CONFIG_IEEE80211W
|
|
/*
|
|
* IGTKdata[variable]:
|
|
* Key ID[2], IPN[6], IGTK[variable]
|
|
*/
|
|
key_len = wpa_cipher_key_len(WPA_CIPHER_AES_128_CMAC);
|
|
if (end - pos >= (int) (2 + 6 + key_len)) {
|
|
sta->igtk_key_id = WPA_GET_LE16(pos);
|
|
wpa_printf(MSG_DEBUG, "mesh: IGTKdata - Key ID %u",
|
|
sta->igtk_key_id);
|
|
pos += 2;
|
|
os_memcpy(sta->igtk_rsc, pos, sizeof(sta->igtk_rsc));
|
|
wpa_hexdump(MSG_DEBUG, "mesh: IGTKdata - IPN",
|
|
sta->igtk_rsc, sizeof(sta->igtk_rsc));
|
|
pos += 6;
|
|
os_memcpy(sta->igtk, pos, key_len);
|
|
sta->igtk_len = key_len;
|
|
wpa_hexdump_key(MSG_DEBUG, "mesh: IGTKdata - IGTK",
|
|
sta->igtk, sta->igtk_len);
|
|
}
|
|
#endif /* CONFIG_IEEE80211W */
|
|
|
|
free:
|
|
os_free(crypt);
|
|
return ret;
|
|
}
|