hostapd/wpa_supplicant/mesh_rsn.c
Masashi Honma 9f2cf23e2e mesh: Add support for PMKSA caching
This patch add functionality of mesh SAE PMKSA caching. If the local STA
already has peer's PMKSA entry in the cache, skip SAE authentication and
start AMPE with the cached value.

If the peer does not support PMKSA caching or does not have the local
STA's PMKSA entry in the cache, AMPE will fail and the PMKSA cache entry
of the peer will be removed. Then STA retries with ordinary SAE
authentication.

If the peer does not support PMKSA caching and the local STA uses
no_auto_peer=1, the local STA can not retry SAE authentication because
NEW_PEER_CANDIDATE event cannot start SAE authentication when
no_auto_peer=1. So this patch extends MESH_PEER_ADD command to use
duration(sec). Throughout the duration, the local STA can start SAE
authentication triggered by NEW_PEER_CANDIDATE even though
no_auto_peer=1.

This commit requires commit 70c93963ed
('SAE: Fix PMKID calculation for PMKSA cache'). Without that commit,
chosen PMK comparison will fail.

Signed-off-by: Masashi Honma <masashi.honma@gmail.com>
2016-03-20 17:56:38 +02:00

615 lines
15 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
#define MESH_AUTH_BLOCK_DURATION 3600
void mesh_auth_timer(void *eloop_ctx, void *user_data)
{
struct wpa_supplicant *wpa_s = eloop_ctx;
struct sta_info *sta = user_data;
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 {
if (sta->sae_auth_retry > MESH_AUTH_RETRY) {
ap_free_sta(wpa_s->ifmsh->bss[0], 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;
if (wpa_s->mesh_auth_block_duration <
MESH_AUTH_BLOCK_DURATION)
wpa_s->mesh_auth_block_duration += 60;
eloop_register_timeout(wpa_s->mesh_auth_block_duration,
0, mesh_auth_timer, wpa_s, sta);
wpa_msg(wpa_s, MSG_INFO, MESH_SAE_AUTH_BLOCKED "addr="
MACSTR " duration=%d",
MAC2STR(sta->addr),
wpa_s->mesh_auth_block_duration);
}
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)
{
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;
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 */
if (random_get_bytes(rsn->mgtk, 16) < 0) {
wpa_deinit(rsn->auth);
return -1;
}
/* group mgmt */
wpa_drv_set_key(rsn->wpa_s, WPA_ALG_IGTK, NULL, 4, 1,
seq, sizeof(seq), rsn->mgtk, sizeof(rsn->mgtk));
/* group privacy / data frames */
wpa_drv_set_key(rsn->wpa_s, WPA_ALG_CCMP, NULL, 1, 1,
seq, sizeof(seq), rsn->mgtk, sizeof(rsn->mgtk));
return 0;
}
static void mesh_rsn_deinit(struct mesh_rsn *rsn)
{
os_memset(rsn->mgtk, 0, sizeof(rsn->mgtk));
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) < 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 = peer, *addr2 = myaddr;
u8 context[AES_BLOCK_SIZE];
/* SAE */
RSN_SELECTOR_PUT(context, wpa_cipher_to_suite(0, WPA_CIPHER_GCMP));
if (os_memcmp(myaddr, peer, ETH_ALEN) < 0) {
addr1 = myaddr;
addr2 = peer;
}
os_memcpy(context + 4, addr1, ETH_ALEN);
os_memcpy(context + 10, 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;
u16 min_lid, max_lid;
size_t nonce_len = sizeof(sta->my_nonce);
size_t lid_len = sizeof(sta->my_lid);
u8 *myaddr = wpa_s->own_addr;
u8 *peer = sta->addr;
/* 2 nonces, 2 linkids, akm suite, 2 mac addrs */
u8 context[64 + 4 + 4 + 12];
ptr = context;
if (os_memcmp(sta->my_nonce, sta->peer_nonce, nonce_len) < 0) {
min = sta->my_nonce;
max = sta->peer_nonce;
} else {
min = sta->peer_nonce;
max = sta->my_nonce;
}
os_memcpy(ptr, min, nonce_len);
os_memcpy(ptr + nonce_len, max, nonce_len);
ptr += 2 * nonce_len;
if (sta->my_lid < sta->peer_lid) {
min_lid = host_to_le16(sta->my_lid);
max_lid = host_to_le16(sta->peer_lid);
} else {
min_lid = host_to_le16(sta->peer_lid);
max_lid = host_to_le16(sta->my_lid);
}
os_memcpy(ptr, &min_lid, lid_len);
os_memcpy(ptr + lid_len, &max_lid, lid_len);
ptr += 2 * lid_len;
/* SAE */
RSN_SELECTOR_PUT(ptr, wpa_cipher_to_suite(0, WPA_CIPHER_GCMP));
ptr += 4;
if (os_memcmp(myaddr, peer, ETH_ALEN) < 0) {
min = myaddr;
max = peer;
} else {
min = peer;
max = myaddr;
}
os_memcpy(ptr, min, ETH_ALEN);
os_memcpy(ptr + ETH_ALEN, max, ETH_ALEN);
sha256_prf(sta->sae->pmk, sizeof(sta->sae->pmk),
"Temporal Key Derivation", context, sizeof(context),
sta->mtk, sizeof(sta->mtk));
return 0;
}
void mesh_rsn_init_ampe_sta(struct wpa_supplicant *wpa_s, struct sta_info *sta)
{
if (random_get_bytes(sta->my_nonce, 32) < 0) {
wpa_printf(MSG_INFO, "mesh: Failed to derive random nonce");
/* TODO: How to handle this more cleanly? */
}
os_memset(sta->peer_nonce, 0, 32);
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 = NULL, *mic_ie = NULL, *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;
if (AES_BLOCK_SIZE + 2 + sizeof(*ampe) + 2 > wpabuf_tailroom(buf)) {
wpa_printf(MSG_ERROR, "protect frame: buffer too small");
return -EINVAL;
}
ampe_ie = os_zalloc(2 + sizeof(*ampe));
if (!ampe_ie) {
wpa_printf(MSG_ERROR, "protect frame: out of memory");
return -ENOMEM;
}
mic_ie = os_zalloc(2 + AES_BLOCK_SIZE);
if (!mic_ie) {
wpa_printf(MSG_ERROR, "protect frame: out of memory");
ret = -ENOMEM;
goto free;
}
/* IE: AMPE */
ampe_ie[0] = WLAN_EID_AMPE;
ampe_ie[1] = sizeof(*ampe);
ampe = (struct ieee80211_ampe_ie *) (ampe_ie + 2);
RSN_SELECTOR_PUT(ampe->selected_pairwise_suite,
wpa_cipher_to_suite(WPA_PROTO_RSN, WPA_CIPHER_CCMP));
os_memcpy(ampe->local_nonce, sta->my_nonce, 32);
os_memcpy(ampe->peer_nonce, sta->peer_nonce, 32);
/* incomplete: see 13.5.4 */
/* TODO: static mgtk for now since we don't support rekeying! */
os_memcpy(ampe->mgtk, rsn->mgtk, 16);
/* TODO: Populate Key RSC */
/* expire in 13 decades or so */
os_memset(ampe->key_expiration, 0xff, 4);
/* IE: MIC */
mic_ie[0] = WLAN_EID_MIC;
mic_ie[1] = AES_BLOCK_SIZE;
wpabuf_put_data(buf, mic_ie, 2);
/* MIC field is output ciphertext */
/* encrypt after MIC */
mic_payload = (u8 *) wpabuf_put(buf, 2 + sizeof(*ampe) +
AES_BLOCK_SIZE);
if (aes_siv_encrypt(sta->aek, ampe_ie, 2 + sizeof(*ampe), 3,
aad, aad_len, mic_payload)) {
wpa_printf(MSG_ERROR, "protect frame: failed to encrypt");
ret = -ENOMEM;
goto free;
}
free:
os_free(ampe_ie);
os_free(mic_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[32] = {};
u8 ampe_eid;
u8 ampe_ie_len;
u8 *ampe_buf, *crypt = NULL;
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 };
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) {
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;
}
ampe_eid = *ampe_buf++;
ampe_ie_len = *ampe_buf++;
if (ampe_eid != WLAN_EID_AMPE ||
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;
if (os_memcmp(ampe->peer_nonce, null_nonce, 32) != 0 &&
os_memcmp(ampe->peer_nonce, sta->my_nonce, 32) != 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));
os_memcpy(sta->mgtk, ampe->mgtk, sizeof(ampe->mgtk));
/* todo parse mgtk expiration */
free:
os_free(crypt);
return ret;
}