663e190b72
IEEE Std 802.11-2020 mandates H2E to be used whenever an SAE password identifier is used. While this was already covered in the implementation, the sae_prepare_commit() function still included an argument for specifying the password identifier since that was used in an old test vector. Now that that test vector has been updated, there is no more need for this argument anymore. Simplify the older non-H2E case to not pass through a pointer to the (not really used) password identifier. Signed-off-by: Jouni Malinen <jouni@codeaurora.org>
795 lines
21 KiB
C
795 lines
21 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,
|
|
size_t *psk_len, int *vlan_id)
|
|
{
|
|
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);
|
|
|
|
if (psk_len)
|
|
*psk_len = PMK_LEN;
|
|
if (vlan_id)
|
|
*vlan_id = 0;
|
|
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,
|
|
enum key_flag key_flag)
|
|
{
|
|
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, key_flag);
|
|
}
|
|
|
|
|
|
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, int ocv)
|
|
{
|
|
struct wpa_auth_config conf;
|
|
static const struct wpa_auth_callbacks cb = {
|
|
.logger = auth_logger,
|
|
.get_psk = auth_get_psk,
|
|
.set_key = auth_set_key,
|
|
.start_ampe = auth_start_ampe,
|
|
};
|
|
u8 seq[6] = {};
|
|
|
|
wpa_printf(MSG_DEBUG, "AUTH: Initializing group state machine");
|
|
|
|
os_memset(&conf, 0, sizeof(conf));
|
|
conf.wpa = WPA_PROTO_RSN;
|
|
conf.wpa_key_mgmt = WPA_KEY_MGMT_SAE;
|
|
conf.wpa_pairwise = rsn->pairwise_cipher;
|
|
conf.rsn_pairwise = rsn->pairwise_cipher;
|
|
conf.wpa_group = rsn->group_cipher;
|
|
conf.eapol_version = 0;
|
|
conf.wpa_group_rekey = -1;
|
|
conf.wpa_group_update_count = 4;
|
|
conf.wpa_pairwise_update_count = 4;
|
|
conf.ieee80211w = ieee80211w;
|
|
if (ieee80211w != NO_MGMT_FRAME_PROTECTION)
|
|
conf.group_mgmt_cipher = rsn->mgmt_group_cipher;
|
|
#ifdef CONFIG_OCV
|
|
conf.ocv = ocv;
|
|
#endif /* CONFIG_OCV */
|
|
|
|
rsn->auth = wpa_init(addr, &conf, &cb, rsn);
|
|
if (rsn->auth == NULL) {
|
|
wpa_printf(MSG_DEBUG, "AUTH: wpa_init() failed");
|
|
return -1;
|
|
}
|
|
|
|
/* TODO: support rekeying */
|
|
rsn->mgtk_len = wpa_cipher_key_len(conf.wpa_group);
|
|
if (random_get_bytes(rsn->mgtk, rsn->mgtk_len) < 0)
|
|
return -1;
|
|
rsn->mgtk_key_id = 1;
|
|
|
|
if (ieee80211w != NO_MGMT_FRAME_PROTECTION) {
|
|
rsn->igtk_len = wpa_cipher_key_len(conf.group_mgmt_cipher);
|
|
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_cipher_to_alg(rsn->mgmt_group_cipher),
|
|
broadcast_ether_addr,
|
|
rsn->igtk_key_id, 1,
|
|
seq, sizeof(seq), rsn->igtk, rsn->igtk_len,
|
|
KEY_FLAG_GROUP_TX_DEFAULT);
|
|
}
|
|
|
|
/* 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_cipher_to_alg(rsn->group_cipher),
|
|
broadcast_ether_addr,
|
|
rsn->mgtk_key_id, 1, seq, sizeof(seq),
|
|
rsn->mgtk, rsn->mgtk_len, KEY_FLAG_GROUP_TX_DEFAULT);
|
|
|
|
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;
|
|
#ifdef CONFIG_PMKSA_CACHE_EXTERNAL
|
|
struct external_pmksa_cache *entry;
|
|
#endif /* CONFIG_PMKSA_CACHE_EXTERNAL */
|
|
|
|
mesh_rsn = os_zalloc(sizeof(*mesh_rsn));
|
|
if (mesh_rsn == NULL)
|
|
return NULL;
|
|
mesh_rsn->wpa_s = wpa_s;
|
|
mesh_rsn->pairwise_cipher = conf->pairwise_cipher;
|
|
mesh_rsn->group_cipher = conf->group_cipher;
|
|
mesh_rsn->mgmt_group_cipher = conf->mgmt_group_cipher;
|
|
|
|
if (__mesh_rsn_auth_init(mesh_rsn, wpa_s->own_addr,
|
|
conf->ieee80211w, conf->ocv) < 0) {
|
|
mesh_rsn_deinit(mesh_rsn);
|
|
os_free(mesh_rsn);
|
|
return NULL;
|
|
}
|
|
|
|
bss->wpa_auth = mesh_rsn->auth;
|
|
|
|
#ifdef CONFIG_PMKSA_CACHE_EXTERNAL
|
|
while ((entry = dl_list_last(&wpa_s->mesh_external_pmksa_cache,
|
|
struct external_pmksa_cache,
|
|
list)) != NULL) {
|
|
int ret;
|
|
|
|
ret = wpa_auth_pmksa_add_entry(bss->wpa_auth,
|
|
entry->pmksa_cache);
|
|
dl_list_del(&entry->list);
|
|
os_free(entry);
|
|
|
|
if (ret < 0)
|
|
return NULL;
|
|
}
|
|
#endif /* CONFIG_PMKSA_CACHE_EXTERNAL */
|
|
|
|
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)
|
|
{
|
|
const char *password;
|
|
|
|
password = ssid->sae_password;
|
|
if (!password)
|
|
password = ssid->passphrase;
|
|
if (!password) {
|
|
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;
|
|
}
|
|
|
|
if (sta->sae->tmp && !sta->sae->tmp->pw_id && ssid->sae_password_id) {
|
|
sta->sae->tmp->pw_id = os_strdup(ssid->sae_password_id);
|
|
if (!sta->sae->tmp->pw_id)
|
|
return -1;
|
|
}
|
|
return sae_prepare_commit(wpa_s->own_addr, sta->addr,
|
|
(u8 *) password, os_strlen(password),
|
|
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, NULL);
|
|
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));
|
|
|
|
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_s->mesh_rsn->pairwise_cipher);
|
|
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);
|
|
if (cat[1] == PLINK_OPEN)
|
|
len += rsn->mgtk_len + WPA_KEY_RSC_LEN + 4;
|
|
if (cat[1] == PLINK_OPEN && rsn->igtk_len)
|
|
len += 2 + 6 + rsn->igtk_len;
|
|
|
|
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);
|
|
if (cat[1] != PLINK_OPEN)
|
|
goto skip_keys;
|
|
|
|
/* 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;
|
|
|
|
/*
|
|
* 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);
|
|
}
|
|
|
|
skip_keys:
|
|
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, sizeof(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 ? (elems->mic - 2) - cat : 0 };
|
|
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, NULL)) {
|
|
wpa_printf(MSG_INFO,
|
|
"Mesh RSN: SAE is not prepared yet");
|
|
return -1;
|
|
}
|
|
mesh_rsn_auth_sae_sta(wpa_s, sta);
|
|
}
|
|
|
|
if (chosen_pmk &&
|
|
(!sta->sae ||
|
|
os_memcmp(chosen_pmk, sta->sae->pmkid, PMKID_LEN) != 0)) {
|
|
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, sizeof(sta->aek), crypt, crypt_len, 3,
|
|
aad, aad_len, ampe_buf)) {
|
|
wpa_printf(MSG_ERROR, "Mesh RSN: frame verification failed!");
|
|
ret = -2;
|
|
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 shall not be included in Mesh Peering Confirm. While the
|
|
* standard does not state the same about IGTKdata, that same constraint
|
|
* needs to apply for it. It makes no sense to include the keys in Mesh
|
|
* Peering Close frames either, so while the standard does not seem to
|
|
* have a shall statement for these, they are described without
|
|
* mentioning GTKdata.
|
|
*
|
|
* An earlier implementation used to add GTKdata to both Mesh Peering
|
|
* Open and Mesh Peering Confirm frames, so ignore the possibly present
|
|
* GTKdata frame without rejecting the frame as a backwards
|
|
* compatibility mechanism.
|
|
*/
|
|
if (cat[1] != PLINK_OPEN) {
|
|
if (end > pos) {
|
|
wpa_hexdump_key(MSG_DEBUG,
|
|
"mesh: Ignore unexpected GTKdata(etc.) fields in the end of AMPE element in Mesh Peering Confirm/Close",
|
|
pos, end - pos);
|
|
}
|
|
goto free;
|
|
}
|
|
|
|
/*
|
|
* GTKdata[variable]:
|
|
* MGTK[variable] || Key RSC[8] || GTKExpirationTime[4]
|
|
*/
|
|
sta->mgtk_key_id = 1; /* FIX: Where to get Key ID? */
|
|
key_len = wpa_cipher_key_len(wpa_s->mesh_rsn->group_cipher);
|
|
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;
|
|
|
|
/*
|
|
* IGTKdata[variable]:
|
|
* Key ID[2], IPN[6], IGTK[variable]
|
|
*/
|
|
key_len = wpa_cipher_key_len(wpa_s->mesh_rsn->mgmt_group_cipher);
|
|
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);
|
|
}
|
|
|
|
free:
|
|
os_free(crypt);
|
|
return ret;
|
|
}
|