24f0507af4
Derive the KDK as part of PMK to PTK derivation if forced by configuration or in case both the local AP and the peer station declare support for secure LTF. Signed-off-by: Ilan Peer <ilan.peer@intel.com>
4813 lines
127 KiB
C
4813 lines
127 KiB
C
/*
|
|
* hostapd - IEEE 802.11r - Fast BSS Transition
|
|
* Copyright (c) 2004-2018, Jouni Malinen <j@w1.fi>
|
|
*
|
|
* 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 "utils/list.h"
|
|
#include "common/ieee802_11_defs.h"
|
|
#include "common/ieee802_11_common.h"
|
|
#include "common/ocv.h"
|
|
#include "common/wpa_ctrl.h"
|
|
#include "drivers/driver.h"
|
|
#include "crypto/aes.h"
|
|
#include "crypto/aes_siv.h"
|
|
#include "crypto/aes_wrap.h"
|
|
#include "crypto/sha384.h"
|
|
#include "crypto/random.h"
|
|
#include "ap_config.h"
|
|
#include "ieee802_11.h"
|
|
#include "wmm.h"
|
|
#include "wpa_auth.h"
|
|
#include "wpa_auth_i.h"
|
|
#include "pmksa_cache_auth.h"
|
|
|
|
|
|
#ifdef CONFIG_IEEE80211R_AP
|
|
|
|
const unsigned int ftRRBseqTimeout = 10;
|
|
const unsigned int ftRRBmaxQueueLen = 100;
|
|
|
|
|
|
static int wpa_ft_send_rrb_auth_resp(struct wpa_state_machine *sm,
|
|
const u8 *current_ap, const u8 *sta_addr,
|
|
u16 status, const u8 *resp_ies,
|
|
size_t resp_ies_len);
|
|
static void ft_finish_pull(struct wpa_state_machine *sm);
|
|
static void wpa_ft_expire_pull(void *eloop_ctx, void *timeout_ctx);
|
|
static void wpa_ft_rrb_seq_timeout(void *eloop_ctx, void *timeout_ctx);
|
|
|
|
struct tlv_list {
|
|
u16 type;
|
|
size_t len;
|
|
const u8 *data;
|
|
};
|
|
|
|
|
|
/**
|
|
* wpa_ft_rrb_decrypt - Decrypt FT RRB message
|
|
* @key: AES-SIV key for AEAD
|
|
* @key_len: Length of key in octets
|
|
* @enc: Pointer to encrypted TLVs
|
|
* @enc_len: Length of encrypted TLVs in octets
|
|
* @auth: Pointer to authenticated TLVs
|
|
* @auth_len: Length of authenticated TLVs in octets
|
|
* @src_addr: MAC address of the frame sender
|
|
* @type: Vendor-specific subtype of the RRB frame (FT_PACKET_*)
|
|
* @plain: Pointer to return the pointer to the allocated plaintext buffer;
|
|
* needs to be freed by the caller if not NULL;
|
|
* will only be returned on success
|
|
* @plain_len: Pointer to return the length of the allocated plaintext buffer
|
|
* in octets
|
|
* Returns: 0 on success, -1 on error
|
|
*/
|
|
static int wpa_ft_rrb_decrypt(const u8 *key, const size_t key_len,
|
|
const u8 *enc, size_t enc_len,
|
|
const u8 *auth, const size_t auth_len,
|
|
const u8 *src_addr, u8 type,
|
|
u8 **plain, size_t *plain_size)
|
|
{
|
|
const u8 *ad[3] = { src_addr, auth, &type };
|
|
size_t ad_len[3] = { ETH_ALEN, auth_len, sizeof(type) };
|
|
|
|
wpa_printf(MSG_DEBUG, "FT(RRB): src_addr=" MACSTR " type=%u",
|
|
MAC2STR(src_addr), type);
|
|
wpa_hexdump_key(MSG_DEBUG, "FT(RRB): decrypt using key", key, key_len);
|
|
wpa_hexdump(MSG_DEBUG, "FT(RRB): encrypted TLVs", enc, enc_len);
|
|
wpa_hexdump(MSG_DEBUG, "FT(RRB): authenticated TLVs", auth, auth_len);
|
|
|
|
if (!key) { /* skip decryption */
|
|
*plain = os_memdup(enc, enc_len);
|
|
if (enc_len > 0 && !*plain)
|
|
goto err;
|
|
|
|
*plain_size = enc_len;
|
|
|
|
return 0;
|
|
}
|
|
|
|
*plain = NULL;
|
|
|
|
/* SIV overhead */
|
|
if (enc_len < AES_BLOCK_SIZE)
|
|
goto err;
|
|
|
|
*plain = os_zalloc(enc_len - AES_BLOCK_SIZE);
|
|
if (!*plain)
|
|
goto err;
|
|
|
|
if (aes_siv_decrypt(key, key_len, enc, enc_len, 3, ad, ad_len,
|
|
*plain) < 0) {
|
|
if (enc_len < AES_BLOCK_SIZE + 2)
|
|
goto err;
|
|
|
|
/* Try to work around Ethernet devices that add extra
|
|
* two octet padding even if the frame is longer than
|
|
* the minimum Ethernet frame. */
|
|
enc_len -= 2;
|
|
if (aes_siv_decrypt(key, key_len, enc, enc_len, 3, ad, ad_len,
|
|
*plain) < 0)
|
|
goto err;
|
|
}
|
|
|
|
*plain_size = enc_len - AES_BLOCK_SIZE;
|
|
wpa_hexdump_key(MSG_DEBUG, "FT(RRB): decrypted TLVs",
|
|
*plain, *plain_size);
|
|
return 0;
|
|
err:
|
|
os_free(*plain);
|
|
*plain = NULL;
|
|
*plain_size = 0;
|
|
|
|
wpa_printf(MSG_ERROR, "FT(RRB): Failed to decrypt");
|
|
|
|
return -1;
|
|
}
|
|
|
|
|
|
/* get first tlv record in packet matching type
|
|
* @data (decrypted) packet
|
|
* @return 0 on success else -1
|
|
*/
|
|
static int wpa_ft_rrb_get_tlv(const u8 *plain, size_t plain_len,
|
|
u16 type, size_t *tlv_len, const u8 **tlv_data)
|
|
{
|
|
const struct ft_rrb_tlv *f;
|
|
size_t left;
|
|
le16 type16;
|
|
size_t len;
|
|
|
|
left = plain_len;
|
|
type16 = host_to_le16(type);
|
|
|
|
while (left >= sizeof(*f)) {
|
|
f = (const struct ft_rrb_tlv *) plain;
|
|
|
|
left -= sizeof(*f);
|
|
plain += sizeof(*f);
|
|
len = le_to_host16(f->len);
|
|
|
|
if (left < len) {
|
|
wpa_printf(MSG_DEBUG, "FT: RRB message truncated");
|
|
break;
|
|
}
|
|
|
|
if (f->type == type16) {
|
|
*tlv_len = len;
|
|
*tlv_data = plain;
|
|
return 0;
|
|
}
|
|
|
|
left -= len;
|
|
plain += len;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
|
|
static void wpa_ft_rrb_dump(const u8 *plain, const size_t plain_len)
|
|
{
|
|
const struct ft_rrb_tlv *f;
|
|
size_t left;
|
|
size_t len;
|
|
|
|
left = plain_len;
|
|
|
|
wpa_printf(MSG_DEBUG, "FT: RRB dump message");
|
|
while (left >= sizeof(*f)) {
|
|
f = (const struct ft_rrb_tlv *) plain;
|
|
|
|
left -= sizeof(*f);
|
|
plain += sizeof(*f);
|
|
len = le_to_host16(f->len);
|
|
|
|
wpa_printf(MSG_DEBUG, "FT: RRB TLV type = %d, len = %zu",
|
|
le_to_host16(f->type), len);
|
|
|
|
if (left < len) {
|
|
wpa_printf(MSG_DEBUG,
|
|
"FT: RRB message truncated: left %zu bytes, need %zu",
|
|
left, len);
|
|
break;
|
|
}
|
|
|
|
wpa_hexdump(MSG_DEBUG, "FT: RRB TLV data", plain, len);
|
|
|
|
left -= len;
|
|
plain += len;
|
|
}
|
|
|
|
if (left > 0)
|
|
wpa_hexdump(MSG_DEBUG, "FT: RRB TLV padding", plain, left);
|
|
|
|
wpa_printf(MSG_DEBUG, "FT: RRB dump message end");
|
|
}
|
|
|
|
|
|
static int cmp_int(const void *a, const void *b)
|
|
{
|
|
int x, y;
|
|
|
|
x = *((int *) a);
|
|
y = *((int *) b);
|
|
return x - y;
|
|
}
|
|
|
|
|
|
static int wpa_ft_rrb_get_tlv_vlan(const u8 *plain, const size_t plain_len,
|
|
struct vlan_description *vlan)
|
|
{
|
|
struct ft_rrb_tlv *f;
|
|
size_t left;
|
|
size_t len;
|
|
int taggedidx;
|
|
int vlan_id;
|
|
int type;
|
|
|
|
left = plain_len;
|
|
taggedidx = 0;
|
|
os_memset(vlan, 0, sizeof(*vlan));
|
|
|
|
while (left >= sizeof(*f)) {
|
|
f = (struct ft_rrb_tlv *) plain;
|
|
|
|
left -= sizeof(*f);
|
|
plain += sizeof(*f);
|
|
|
|
len = le_to_host16(f->len);
|
|
type = le_to_host16(f->type);
|
|
|
|
if (left < len) {
|
|
wpa_printf(MSG_DEBUG, "FT: RRB message truncated");
|
|
return -1;
|
|
}
|
|
|
|
if (type != FT_RRB_VLAN_UNTAGGED && type != FT_RRB_VLAN_TAGGED)
|
|
goto skip;
|
|
|
|
if (type == FT_RRB_VLAN_UNTAGGED && len != sizeof(le16)) {
|
|
wpa_printf(MSG_DEBUG,
|
|
"FT: RRB VLAN_UNTAGGED invalid length");
|
|
return -1;
|
|
}
|
|
|
|
if (type == FT_RRB_VLAN_TAGGED && len % sizeof(le16) != 0) {
|
|
wpa_printf(MSG_DEBUG,
|
|
"FT: RRB VLAN_TAGGED invalid length");
|
|
return -1;
|
|
}
|
|
|
|
while (len >= sizeof(le16)) {
|
|
vlan_id = WPA_GET_LE16(plain);
|
|
plain += sizeof(le16);
|
|
left -= sizeof(le16);
|
|
len -= sizeof(le16);
|
|
|
|
if (vlan_id <= 0 || vlan_id > MAX_VLAN_ID) {
|
|
wpa_printf(MSG_DEBUG,
|
|
"FT: RRB VLAN ID invalid %d",
|
|
vlan_id);
|
|
continue;
|
|
}
|
|
|
|
if (type == FT_RRB_VLAN_UNTAGGED)
|
|
vlan->untagged = vlan_id;
|
|
|
|
if (type == FT_RRB_VLAN_TAGGED &&
|
|
taggedidx < MAX_NUM_TAGGED_VLAN) {
|
|
vlan->tagged[taggedidx] = vlan_id;
|
|
taggedidx++;
|
|
} else if (type == FT_RRB_VLAN_TAGGED) {
|
|
wpa_printf(MSG_DEBUG, "FT: RRB too many VLANs");
|
|
}
|
|
}
|
|
|
|
skip:
|
|
left -= len;
|
|
plain += len;
|
|
}
|
|
|
|
if (taggedidx)
|
|
qsort(vlan->tagged, taggedidx, sizeof(int), cmp_int);
|
|
|
|
vlan->notempty = vlan->untagged || vlan->tagged[0];
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static size_t wpa_ft_tlv_len(const struct tlv_list *tlvs)
|
|
{
|
|
size_t tlv_len = 0;
|
|
int i;
|
|
|
|
if (!tlvs)
|
|
return 0;
|
|
|
|
for (i = 0; tlvs[i].type != FT_RRB_LAST_EMPTY; i++) {
|
|
tlv_len += sizeof(struct ft_rrb_tlv);
|
|
tlv_len += tlvs[i].len;
|
|
}
|
|
|
|
return tlv_len;
|
|
}
|
|
|
|
|
|
static size_t wpa_ft_tlv_lin(const struct tlv_list *tlvs, u8 *start,
|
|
u8 *endpos)
|
|
{
|
|
int i;
|
|
size_t tlv_len;
|
|
struct ft_rrb_tlv *hdr;
|
|
u8 *pos;
|
|
|
|
if (!tlvs)
|
|
return 0;
|
|
|
|
tlv_len = 0;
|
|
pos = start;
|
|
for (i = 0; tlvs[i].type != FT_RRB_LAST_EMPTY; i++) {
|
|
if (tlv_len + sizeof(*hdr) > (size_t) (endpos - start))
|
|
return tlv_len;
|
|
tlv_len += sizeof(*hdr);
|
|
hdr = (struct ft_rrb_tlv *) pos;
|
|
hdr->type = host_to_le16(tlvs[i].type);
|
|
hdr->len = host_to_le16(tlvs[i].len);
|
|
pos = start + tlv_len;
|
|
|
|
if (tlv_len + tlvs[i].len > (size_t) (endpos - start))
|
|
return tlv_len;
|
|
if (tlvs[i].len == 0)
|
|
continue;
|
|
tlv_len += tlvs[i].len;
|
|
os_memcpy(pos, tlvs[i].data, tlvs[i].len);
|
|
pos = start + tlv_len;
|
|
}
|
|
|
|
return tlv_len;
|
|
}
|
|
|
|
|
|
static size_t wpa_ft_vlan_len(const struct vlan_description *vlan)
|
|
{
|
|
size_t tlv_len = 0;
|
|
int i;
|
|
|
|
if (!vlan || !vlan->notempty)
|
|
return 0;
|
|
|
|
if (vlan->untagged) {
|
|
tlv_len += sizeof(struct ft_rrb_tlv);
|
|
tlv_len += sizeof(le16);
|
|
}
|
|
if (vlan->tagged[0])
|
|
tlv_len += sizeof(struct ft_rrb_tlv);
|
|
for (i = 0; i < MAX_NUM_TAGGED_VLAN && vlan->tagged[i]; i++)
|
|
tlv_len += sizeof(le16);
|
|
|
|
return tlv_len;
|
|
}
|
|
|
|
|
|
static size_t wpa_ft_vlan_lin(const struct vlan_description *vlan,
|
|
u8 *start, u8 *endpos)
|
|
{
|
|
size_t tlv_len;
|
|
int i, len;
|
|
struct ft_rrb_tlv *hdr;
|
|
u8 *pos = start;
|
|
|
|
if (!vlan || !vlan->notempty)
|
|
return 0;
|
|
|
|
tlv_len = 0;
|
|
if (vlan->untagged) {
|
|
tlv_len += sizeof(*hdr);
|
|
if (start + tlv_len > endpos)
|
|
return tlv_len;
|
|
hdr = (struct ft_rrb_tlv *) pos;
|
|
hdr->type = host_to_le16(FT_RRB_VLAN_UNTAGGED);
|
|
hdr->len = host_to_le16(sizeof(le16));
|
|
pos = start + tlv_len;
|
|
|
|
tlv_len += sizeof(le16);
|
|
if (start + tlv_len > endpos)
|
|
return tlv_len;
|
|
WPA_PUT_LE16(pos, vlan->untagged);
|
|
pos = start + tlv_len;
|
|
}
|
|
|
|
if (!vlan->tagged[0])
|
|
return tlv_len;
|
|
|
|
tlv_len += sizeof(*hdr);
|
|
if (start + tlv_len > endpos)
|
|
return tlv_len;
|
|
hdr = (struct ft_rrb_tlv *) pos;
|
|
hdr->type = host_to_le16(FT_RRB_VLAN_TAGGED);
|
|
len = 0; /* len is computed below */
|
|
pos = start + tlv_len;
|
|
|
|
for (i = 0; i < MAX_NUM_TAGGED_VLAN && vlan->tagged[i]; i++) {
|
|
tlv_len += sizeof(le16);
|
|
if (start + tlv_len > endpos)
|
|
break;
|
|
len += sizeof(le16);
|
|
WPA_PUT_LE16(pos, vlan->tagged[i]);
|
|
pos = start + tlv_len;
|
|
}
|
|
|
|
hdr->len = host_to_le16(len);
|
|
|
|
return tlv_len;
|
|
}
|
|
|
|
|
|
static int wpa_ft_rrb_lin(const struct tlv_list *tlvs1,
|
|
const struct tlv_list *tlvs2,
|
|
const struct vlan_description *vlan,
|
|
u8 **plain, size_t *plain_len)
|
|
{
|
|
u8 *pos, *endpos;
|
|
size_t tlv_len;
|
|
|
|
tlv_len = wpa_ft_tlv_len(tlvs1);
|
|
tlv_len += wpa_ft_tlv_len(tlvs2);
|
|
tlv_len += wpa_ft_vlan_len(vlan);
|
|
|
|
*plain_len = tlv_len;
|
|
*plain = os_zalloc(tlv_len);
|
|
if (!*plain) {
|
|
wpa_printf(MSG_ERROR, "FT: Failed to allocate plaintext");
|
|
goto err;
|
|
}
|
|
|
|
pos = *plain;
|
|
endpos = *plain + tlv_len;
|
|
pos += wpa_ft_tlv_lin(tlvs1, pos, endpos);
|
|
pos += wpa_ft_tlv_lin(tlvs2, pos, endpos);
|
|
pos += wpa_ft_vlan_lin(vlan, pos, endpos);
|
|
|
|
/* sanity check */
|
|
if (pos != endpos) {
|
|
wpa_printf(MSG_ERROR, "FT: Length error building RRB");
|
|
goto err;
|
|
}
|
|
|
|
return 0;
|
|
|
|
err:
|
|
os_free(*plain);
|
|
*plain = NULL;
|
|
*plain_len = 0;
|
|
return -1;
|
|
}
|
|
|
|
|
|
static int wpa_ft_rrb_encrypt(const u8 *key, const size_t key_len,
|
|
const u8 *plain, const size_t plain_len,
|
|
const u8 *auth, const size_t auth_len,
|
|
const u8 *src_addr, u8 type, u8 *enc)
|
|
{
|
|
const u8 *ad[3] = { src_addr, auth, &type };
|
|
size_t ad_len[3] = { ETH_ALEN, auth_len, sizeof(type) };
|
|
|
|
wpa_printf(MSG_DEBUG, "FT(RRB): src_addr=" MACSTR " type=%u",
|
|
MAC2STR(src_addr), type);
|
|
wpa_hexdump_key(MSG_DEBUG, "FT(RRB): plaintext message",
|
|
plain, plain_len);
|
|
wpa_hexdump_key(MSG_DEBUG, "FT(RRB): encrypt using key", key, key_len);
|
|
wpa_hexdump(MSG_DEBUG, "FT(RRB): authenticated TLVs", auth, auth_len);
|
|
|
|
if (!key) {
|
|
/* encryption not needed, return plaintext as packet */
|
|
os_memcpy(enc, plain, plain_len);
|
|
} else if (aes_siv_encrypt(key, key_len, plain, plain_len,
|
|
3, ad, ad_len, enc) < 0) {
|
|
wpa_printf(MSG_ERROR, "FT: Failed to encrypt RRB-OUI message");
|
|
return -1;
|
|
}
|
|
wpa_hexdump(MSG_DEBUG, "FT(RRB): encrypted TLVs",
|
|
enc, plain_len + AES_BLOCK_SIZE);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* wpa_ft_rrb_build - Build and encrypt an FT RRB message
|
|
* @key: AES-SIV key for AEAD
|
|
* @key_len: Length of key in octets
|
|
* @tlvs_enc0: First set of to-be-encrypted TLVs
|
|
* @tlvs_enc1: Second set of to-be-encrypted TLVs
|
|
* @tlvs_auth: Set of to-be-authenticated TLVs
|
|
* @src_addr: MAC address of the frame sender
|
|
* @type: Vendor-specific subtype of the RRB frame (FT_PACKET_*)
|
|
* @packet Pointer to return the pointer to the allocated packet buffer;
|
|
* needs to be freed by the caller if not null;
|
|
* will only be returned on success
|
|
* @packet_len: Pointer to return the length of the allocated buffer in octets
|
|
* Returns: 0 on success, -1 on error
|
|
*/
|
|
static int wpa_ft_rrb_build(const u8 *key, const size_t key_len,
|
|
const struct tlv_list *tlvs_enc0,
|
|
const struct tlv_list *tlvs_enc1,
|
|
const struct tlv_list *tlvs_auth,
|
|
const struct vlan_description *vlan,
|
|
const u8 *src_addr, u8 type,
|
|
u8 **packet, size_t *packet_len)
|
|
{
|
|
u8 *plain = NULL, *auth = NULL, *pos, *tmp;
|
|
size_t plain_len = 0, auth_len = 0;
|
|
int ret = -1;
|
|
size_t pad_len = 0;
|
|
|
|
*packet = NULL;
|
|
if (wpa_ft_rrb_lin(tlvs_enc0, tlvs_enc1, vlan, &plain, &plain_len) < 0)
|
|
goto out;
|
|
|
|
if (wpa_ft_rrb_lin(tlvs_auth, NULL, NULL, &auth, &auth_len) < 0)
|
|
goto out;
|
|
|
|
*packet_len = sizeof(u16) + auth_len + plain_len;
|
|
if (key)
|
|
*packet_len += AES_BLOCK_SIZE;
|
|
#define RRB_MIN_MSG_LEN 64
|
|
if (*packet_len < RRB_MIN_MSG_LEN) {
|
|
pad_len = RRB_MIN_MSG_LEN - *packet_len;
|
|
if (pad_len < sizeof(struct ft_rrb_tlv))
|
|
pad_len = sizeof(struct ft_rrb_tlv);
|
|
wpa_printf(MSG_DEBUG,
|
|
"FT: Pad message to minimum Ethernet frame length (%d --> %d)",
|
|
(int) *packet_len, (int) (*packet_len + pad_len));
|
|
*packet_len += pad_len;
|
|
tmp = os_realloc(auth, auth_len + pad_len);
|
|
if (!tmp)
|
|
goto out;
|
|
auth = tmp;
|
|
pos = auth + auth_len;
|
|
WPA_PUT_LE16(pos, FT_RRB_LAST_EMPTY);
|
|
pos += 2;
|
|
WPA_PUT_LE16(pos, pad_len - sizeof(struct ft_rrb_tlv));
|
|
pos += 2;
|
|
os_memset(pos, 0, pad_len - sizeof(struct ft_rrb_tlv));
|
|
auth_len += pad_len;
|
|
|
|
}
|
|
*packet = os_zalloc(*packet_len);
|
|
if (!*packet)
|
|
goto out;
|
|
|
|
pos = *packet;
|
|
WPA_PUT_LE16(pos, auth_len);
|
|
pos += 2;
|
|
os_memcpy(pos, auth, auth_len);
|
|
pos += auth_len;
|
|
if (wpa_ft_rrb_encrypt(key, key_len, plain, plain_len, auth,
|
|
auth_len, src_addr, type, pos) < 0)
|
|
goto out;
|
|
wpa_hexdump(MSG_MSGDUMP, "FT: RRB frame payload", *packet, *packet_len);
|
|
|
|
ret = 0;
|
|
|
|
out:
|
|
bin_clear_free(plain, plain_len);
|
|
os_free(auth);
|
|
|
|
if (ret) {
|
|
wpa_printf(MSG_ERROR, "FT: Failed to build RRB-OUI message");
|
|
os_free(*packet);
|
|
*packet = NULL;
|
|
*packet_len = 0;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
#define RRB_GET_SRC(srcfield, type, field, txt, checklength) do { \
|
|
if (wpa_ft_rrb_get_tlv(srcfield, srcfield##_len, type, \
|
|
&f_##field##_len, &f_##field) < 0 || \
|
|
(checklength > 0 && ((size_t) checklength) != f_##field##_len)) { \
|
|
wpa_printf(MSG_INFO, "FT: Missing required " #field \
|
|
" in %s from " MACSTR, txt, MAC2STR(src_addr)); \
|
|
wpa_ft_rrb_dump(srcfield, srcfield##_len); \
|
|
goto out; \
|
|
} \
|
|
} while (0)
|
|
|
|
#define RRB_GET(type, field, txt, checklength) \
|
|
RRB_GET_SRC(plain, type, field, txt, checklength)
|
|
#define RRB_GET_AUTH(type, field, txt, checklength) \
|
|
RRB_GET_SRC(auth, type, field, txt, checklength)
|
|
|
|
#define RRB_GET_OPTIONAL_SRC(srcfield, type, field, txt, checklength) do { \
|
|
if (wpa_ft_rrb_get_tlv(srcfield, srcfield##_len, type, \
|
|
&f_##field##_len, &f_##field) < 0 || \
|
|
(checklength > 0 && ((size_t) checklength) != f_##field##_len)) { \
|
|
wpa_printf(MSG_DEBUG, "FT: Missing optional " #field \
|
|
" in %s from " MACSTR, txt, MAC2STR(src_addr)); \
|
|
f_##field##_len = 0; \
|
|
f_##field = NULL; \
|
|
} \
|
|
} while (0)
|
|
|
|
#define RRB_GET_OPTIONAL(type, field, txt, checklength) \
|
|
RRB_GET_OPTIONAL_SRC(plain, type, field, txt, checklength)
|
|
#define RRB_GET_OPTIONAL_AUTH(type, field, txt, checklength) \
|
|
RRB_GET_OPTIONAL_SRC(auth, type, field, txt, checklength)
|
|
|
|
static int wpa_ft_rrb_send(struct wpa_authenticator *wpa_auth, const u8 *dst,
|
|
const u8 *data, size_t data_len)
|
|
{
|
|
if (wpa_auth->cb->send_ether == NULL)
|
|
return -1;
|
|
wpa_printf(MSG_DEBUG, "FT: RRB send to " MACSTR, MAC2STR(dst));
|
|
return wpa_auth->cb->send_ether(wpa_auth->cb_ctx, dst, ETH_P_RRB,
|
|
data, data_len);
|
|
}
|
|
|
|
|
|
static int wpa_ft_rrb_oui_send(struct wpa_authenticator *wpa_auth,
|
|
const u8 *dst, u8 oui_suffix,
|
|
const u8 *data, size_t data_len)
|
|
{
|
|
if (!wpa_auth->cb->send_oui)
|
|
return -1;
|
|
wpa_printf(MSG_DEBUG, "FT: RRB-OUI type %u send to " MACSTR " (len=%u)",
|
|
oui_suffix, MAC2STR(dst), (unsigned int) data_len);
|
|
return wpa_auth->cb->send_oui(wpa_auth->cb_ctx, dst, oui_suffix, data,
|
|
data_len);
|
|
}
|
|
|
|
|
|
static int wpa_ft_action_send(struct wpa_authenticator *wpa_auth,
|
|
const u8 *dst, const u8 *data, size_t data_len)
|
|
{
|
|
if (wpa_auth->cb->send_ft_action == NULL)
|
|
return -1;
|
|
return wpa_auth->cb->send_ft_action(wpa_auth->cb_ctx, dst,
|
|
data, data_len);
|
|
}
|
|
|
|
|
|
static const u8 * wpa_ft_get_psk(struct wpa_authenticator *wpa_auth,
|
|
const u8 *addr, const u8 *p2p_dev_addr,
|
|
const u8 *prev_psk)
|
|
{
|
|
if (wpa_auth->cb->get_psk == NULL)
|
|
return NULL;
|
|
return wpa_auth->cb->get_psk(wpa_auth->cb_ctx, addr, p2p_dev_addr,
|
|
prev_psk, NULL, NULL);
|
|
}
|
|
|
|
|
|
static struct wpa_state_machine *
|
|
wpa_ft_add_sta(struct wpa_authenticator *wpa_auth, const u8 *sta_addr)
|
|
{
|
|
if (wpa_auth->cb->add_sta == NULL)
|
|
return NULL;
|
|
return wpa_auth->cb->add_sta(wpa_auth->cb_ctx, sta_addr);
|
|
}
|
|
|
|
|
|
static int wpa_ft_set_vlan(struct wpa_authenticator *wpa_auth,
|
|
const u8 *sta_addr, struct vlan_description *vlan)
|
|
{
|
|
if (!wpa_auth->cb->set_vlan)
|
|
return -1;
|
|
return wpa_auth->cb->set_vlan(wpa_auth->cb_ctx, sta_addr, vlan);
|
|
}
|
|
|
|
|
|
static int wpa_ft_get_vlan(struct wpa_authenticator *wpa_auth,
|
|
const u8 *sta_addr, struct vlan_description *vlan)
|
|
{
|
|
if (!wpa_auth->cb->get_vlan)
|
|
return -1;
|
|
return wpa_auth->cb->get_vlan(wpa_auth->cb_ctx, sta_addr, vlan);
|
|
}
|
|
|
|
|
|
static int
|
|
wpa_ft_set_identity(struct wpa_authenticator *wpa_auth, const u8 *sta_addr,
|
|
const u8 *identity, size_t identity_len)
|
|
{
|
|
if (!wpa_auth->cb->set_identity)
|
|
return -1;
|
|
return wpa_auth->cb->set_identity(wpa_auth->cb_ctx, sta_addr, identity,
|
|
identity_len);
|
|
}
|
|
|
|
|
|
static size_t
|
|
wpa_ft_get_identity(struct wpa_authenticator *wpa_auth, const u8 *sta_addr,
|
|
const u8 **buf)
|
|
{
|
|
*buf = NULL;
|
|
if (!wpa_auth->cb->get_identity)
|
|
return 0;
|
|
return wpa_auth->cb->get_identity(wpa_auth->cb_ctx, sta_addr, buf);
|
|
}
|
|
|
|
|
|
static int
|
|
wpa_ft_set_radius_cui(struct wpa_authenticator *wpa_auth, const u8 *sta_addr,
|
|
const u8 *radius_cui, size_t radius_cui_len)
|
|
{
|
|
if (!wpa_auth->cb->set_radius_cui)
|
|
return -1;
|
|
return wpa_auth->cb->set_radius_cui(wpa_auth->cb_ctx, sta_addr,
|
|
radius_cui, radius_cui_len);
|
|
}
|
|
|
|
|
|
static size_t
|
|
wpa_ft_get_radius_cui(struct wpa_authenticator *wpa_auth, const u8 *sta_addr,
|
|
const u8 **buf)
|
|
{
|
|
*buf = NULL;
|
|
if (!wpa_auth->cb->get_radius_cui)
|
|
return 0;
|
|
return wpa_auth->cb->get_radius_cui(wpa_auth->cb_ctx, sta_addr, buf);
|
|
}
|
|
|
|
|
|
static void
|
|
wpa_ft_set_session_timeout(struct wpa_authenticator *wpa_auth,
|
|
const u8 *sta_addr, int session_timeout)
|
|
{
|
|
if (!wpa_auth->cb->set_session_timeout)
|
|
return;
|
|
wpa_auth->cb->set_session_timeout(wpa_auth->cb_ctx, sta_addr,
|
|
session_timeout);
|
|
}
|
|
|
|
|
|
static int
|
|
wpa_ft_get_session_timeout(struct wpa_authenticator *wpa_auth,
|
|
const u8 *sta_addr)
|
|
{
|
|
if (!wpa_auth->cb->get_session_timeout)
|
|
return 0;
|
|
return wpa_auth->cb->get_session_timeout(wpa_auth->cb_ctx, sta_addr);
|
|
}
|
|
|
|
|
|
static int wpa_ft_add_tspec(struct wpa_authenticator *wpa_auth,
|
|
const u8 *sta_addr,
|
|
u8 *tspec_ie, size_t tspec_ielen)
|
|
{
|
|
if (wpa_auth->cb->add_tspec == NULL) {
|
|
wpa_printf(MSG_DEBUG, "FT: add_tspec is not initialized");
|
|
return -1;
|
|
}
|
|
return wpa_auth->cb->add_tspec(wpa_auth->cb_ctx, sta_addr, tspec_ie,
|
|
tspec_ielen);
|
|
}
|
|
|
|
|
|
#ifdef CONFIG_OCV
|
|
static int wpa_channel_info(struct wpa_authenticator *wpa_auth,
|
|
struct wpa_channel_info *ci)
|
|
{
|
|
if (!wpa_auth->cb->channel_info)
|
|
return -1;
|
|
return wpa_auth->cb->channel_info(wpa_auth->cb_ctx, ci);
|
|
}
|
|
#endif /* CONFIG_OCV */
|
|
|
|
|
|
int wpa_write_mdie(struct wpa_auth_config *conf, u8 *buf, size_t len)
|
|
{
|
|
u8 *pos = buf;
|
|
u8 capab;
|
|
if (len < 2 + sizeof(struct rsn_mdie))
|
|
return -1;
|
|
|
|
*pos++ = WLAN_EID_MOBILITY_DOMAIN;
|
|
*pos++ = MOBILITY_DOMAIN_ID_LEN + 1;
|
|
os_memcpy(pos, conf->mobility_domain, MOBILITY_DOMAIN_ID_LEN);
|
|
pos += MOBILITY_DOMAIN_ID_LEN;
|
|
capab = 0;
|
|
if (conf->ft_over_ds)
|
|
capab |= RSN_FT_CAPAB_FT_OVER_DS;
|
|
*pos++ = capab;
|
|
|
|
return pos - buf;
|
|
}
|
|
|
|
|
|
int wpa_write_ftie(struct wpa_auth_config *conf, int use_sha384,
|
|
const u8 *r0kh_id, size_t r0kh_id_len,
|
|
const u8 *anonce, const u8 *snonce,
|
|
u8 *buf, size_t len, const u8 *subelem,
|
|
size_t subelem_len, int rsnxe_used)
|
|
{
|
|
u8 *pos = buf, *ielen;
|
|
size_t hdrlen = use_sha384 ? sizeof(struct rsn_ftie_sha384) :
|
|
sizeof(struct rsn_ftie);
|
|
|
|
if (len < 2 + hdrlen + 2 + FT_R1KH_ID_LEN + 2 + r0kh_id_len +
|
|
subelem_len)
|
|
return -1;
|
|
|
|
*pos++ = WLAN_EID_FAST_BSS_TRANSITION;
|
|
ielen = pos++;
|
|
|
|
if (use_sha384) {
|
|
struct rsn_ftie_sha384 *hdr = (struct rsn_ftie_sha384 *) pos;
|
|
|
|
os_memset(hdr, 0, sizeof(*hdr));
|
|
pos += sizeof(*hdr);
|
|
WPA_PUT_LE16(hdr->mic_control, !!rsnxe_used);
|
|
if (anonce)
|
|
os_memcpy(hdr->anonce, anonce, WPA_NONCE_LEN);
|
|
if (snonce)
|
|
os_memcpy(hdr->snonce, snonce, WPA_NONCE_LEN);
|
|
} else {
|
|
struct rsn_ftie *hdr = (struct rsn_ftie *) pos;
|
|
|
|
os_memset(hdr, 0, sizeof(*hdr));
|
|
pos += sizeof(*hdr);
|
|
WPA_PUT_LE16(hdr->mic_control, !!rsnxe_used);
|
|
if (anonce)
|
|
os_memcpy(hdr->anonce, anonce, WPA_NONCE_LEN);
|
|
if (snonce)
|
|
os_memcpy(hdr->snonce, snonce, WPA_NONCE_LEN);
|
|
}
|
|
|
|
/* Optional Parameters */
|
|
*pos++ = FTIE_SUBELEM_R1KH_ID;
|
|
*pos++ = FT_R1KH_ID_LEN;
|
|
os_memcpy(pos, conf->r1_key_holder, FT_R1KH_ID_LEN);
|
|
pos += FT_R1KH_ID_LEN;
|
|
|
|
if (r0kh_id) {
|
|
*pos++ = FTIE_SUBELEM_R0KH_ID;
|
|
*pos++ = r0kh_id_len;
|
|
os_memcpy(pos, r0kh_id, r0kh_id_len);
|
|
pos += r0kh_id_len;
|
|
}
|
|
|
|
if (subelem) {
|
|
os_memcpy(pos, subelem, subelem_len);
|
|
pos += subelem_len;
|
|
}
|
|
|
|
*ielen = pos - buf - 2;
|
|
|
|
return pos - buf;
|
|
}
|
|
|
|
|
|
/* A packet to be handled after seq response */
|
|
struct ft_remote_item {
|
|
struct dl_list list;
|
|
|
|
u8 nonce[FT_RRB_NONCE_LEN];
|
|
struct os_reltime nonce_ts;
|
|
|
|
u8 src_addr[ETH_ALEN];
|
|
u8 *enc;
|
|
size_t enc_len;
|
|
u8 *auth;
|
|
size_t auth_len;
|
|
int (*cb)(struct wpa_authenticator *wpa_auth,
|
|
const u8 *src_addr,
|
|
const u8 *enc, size_t enc_len,
|
|
const u8 *auth, size_t auth_len,
|
|
int no_defer);
|
|
};
|
|
|
|
|
|
static void wpa_ft_rrb_seq_free(struct ft_remote_item *item)
|
|
{
|
|
eloop_cancel_timeout(wpa_ft_rrb_seq_timeout, ELOOP_ALL_CTX, item);
|
|
dl_list_del(&item->list);
|
|
bin_clear_free(item->enc, item->enc_len);
|
|
os_free(item->auth);
|
|
os_free(item);
|
|
}
|
|
|
|
|
|
static void wpa_ft_rrb_seq_flush(struct wpa_authenticator *wpa_auth,
|
|
struct ft_remote_seq *rkh_seq, int cb)
|
|
{
|
|
struct ft_remote_item *item, *n;
|
|
|
|
dl_list_for_each_safe(item, n, &rkh_seq->rx.queue,
|
|
struct ft_remote_item, list) {
|
|
if (cb && item->cb)
|
|
item->cb(wpa_auth, item->src_addr, item->enc,
|
|
item->enc_len, item->auth, item->auth_len, 1);
|
|
wpa_ft_rrb_seq_free(item);
|
|
}
|
|
}
|
|
|
|
|
|
static void wpa_ft_rrb_seq_timeout(void *eloop_ctx, void *timeout_ctx)
|
|
{
|
|
struct ft_remote_item *item = timeout_ctx;
|
|
|
|
wpa_ft_rrb_seq_free(item);
|
|
}
|
|
|
|
|
|
static int
|
|
wpa_ft_rrb_seq_req(struct wpa_authenticator *wpa_auth,
|
|
struct ft_remote_seq *rkh_seq, const u8 *src_addr,
|
|
const u8 *f_r0kh_id, size_t f_r0kh_id_len,
|
|
const u8 *f_r1kh_id, const u8 *key, size_t key_len,
|
|
const u8 *enc, size_t enc_len,
|
|
const u8 *auth, size_t auth_len,
|
|
int (*cb)(struct wpa_authenticator *wpa_auth,
|
|
const u8 *src_addr,
|
|
const u8 *enc, size_t enc_len,
|
|
const u8 *auth, size_t auth_len,
|
|
int no_defer))
|
|
{
|
|
struct ft_remote_item *item = NULL;
|
|
u8 *packet = NULL;
|
|
size_t packet_len;
|
|
struct tlv_list seq_req_auth[] = {
|
|
{ .type = FT_RRB_NONCE, .len = FT_RRB_NONCE_LEN,
|
|
.data = NULL /* to be filled: item->nonce */ },
|
|
{ .type = FT_RRB_R0KH_ID, .len = f_r0kh_id_len,
|
|
.data = f_r0kh_id },
|
|
{ .type = FT_RRB_R1KH_ID, .len = FT_R1KH_ID_LEN,
|
|
.data = f_r1kh_id },
|
|
{ .type = FT_RRB_LAST_EMPTY, .len = 0, .data = NULL },
|
|
};
|
|
|
|
if (dl_list_len(&rkh_seq->rx.queue) >= ftRRBmaxQueueLen) {
|
|
wpa_printf(MSG_DEBUG, "FT: Sequence number queue too long");
|
|
goto err;
|
|
}
|
|
|
|
wpa_printf(MSG_DEBUG, "FT: Send sequence number request from " MACSTR
|
|
" to " MACSTR,
|
|
MAC2STR(wpa_auth->addr), MAC2STR(src_addr));
|
|
item = os_zalloc(sizeof(*item));
|
|
if (!item)
|
|
goto err;
|
|
|
|
os_memcpy(item->src_addr, src_addr, ETH_ALEN);
|
|
item->cb = cb;
|
|
|
|
if (random_get_bytes(item->nonce, FT_RRB_NONCE_LEN) < 0) {
|
|
wpa_printf(MSG_DEBUG, "FT: Seq num nonce: out of random bytes");
|
|
goto err;
|
|
}
|
|
|
|
if (os_get_reltime(&item->nonce_ts) < 0)
|
|
goto err;
|
|
|
|
if (enc && enc_len > 0) {
|
|
item->enc = os_memdup(enc, enc_len);
|
|
item->enc_len = enc_len;
|
|
if (!item->enc)
|
|
goto err;
|
|
}
|
|
|
|
if (auth && auth_len > 0) {
|
|
item->auth = os_memdup(auth, auth_len);
|
|
item->auth_len = auth_len;
|
|
if (!item->auth)
|
|
goto err;
|
|
}
|
|
|
|
eloop_register_timeout(ftRRBseqTimeout, 0, wpa_ft_rrb_seq_timeout,
|
|
wpa_auth, item);
|
|
|
|
seq_req_auth[0].data = item->nonce;
|
|
|
|
if (wpa_ft_rrb_build(key, key_len, NULL, NULL, seq_req_auth, NULL,
|
|
wpa_auth->addr, FT_PACKET_R0KH_R1KH_SEQ_REQ,
|
|
&packet, &packet_len) < 0) {
|
|
item = NULL; /* some other seq resp might still accept this */
|
|
goto err;
|
|
}
|
|
|
|
dl_list_add(&rkh_seq->rx.queue, &item->list);
|
|
|
|
wpa_ft_rrb_oui_send(wpa_auth, src_addr, FT_PACKET_R0KH_R1KH_SEQ_REQ,
|
|
packet, packet_len);
|
|
|
|
os_free(packet);
|
|
|
|
return 0;
|
|
err:
|
|
wpa_printf(MSG_DEBUG, "FT: Failed to send sequence number request");
|
|
if (item) {
|
|
os_free(item->auth);
|
|
bin_clear_free(item->enc, item->enc_len);
|
|
os_free(item);
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
|
|
#define FT_RRB_SEQ_OK 0
|
|
#define FT_RRB_SEQ_DROP 1
|
|
#define FT_RRB_SEQ_DEFER 2
|
|
|
|
static int
|
|
wpa_ft_rrb_seq_chk(struct ft_remote_seq *rkh_seq, const u8 *src_addr,
|
|
const u8 *enc, size_t enc_len,
|
|
const u8 *auth, size_t auth_len,
|
|
const char *msgtype, int no_defer)
|
|
{
|
|
const u8 *f_seq;
|
|
size_t f_seq_len;
|
|
const struct ft_rrb_seq *msg_both;
|
|
u32 msg_seq, msg_off, rkh_off;
|
|
struct os_reltime now;
|
|
unsigned int i;
|
|
|
|
RRB_GET_AUTH(FT_RRB_SEQ, seq, msgtype, sizeof(*msg_both));
|
|
wpa_hexdump(MSG_DEBUG, "FT: sequence number", f_seq, f_seq_len);
|
|
msg_both = (const struct ft_rrb_seq *) f_seq;
|
|
|
|
if (rkh_seq->rx.num_last == 0) {
|
|
/* first packet from remote */
|
|
goto defer;
|
|
}
|
|
|
|
if (le_to_host32(msg_both->dom) != rkh_seq->rx.dom) {
|
|
/* remote might have rebooted */
|
|
goto defer;
|
|
}
|
|
|
|
if (os_get_reltime(&now) == 0) {
|
|
u32 msg_ts_now_remote, msg_ts_off;
|
|
struct os_reltime now_remote;
|
|
|
|
os_reltime_sub(&now, &rkh_seq->rx.time_offset, &now_remote);
|
|
msg_ts_now_remote = now_remote.sec;
|
|
msg_ts_off = le_to_host32(msg_both->ts) -
|
|
(msg_ts_now_remote - ftRRBseqTimeout);
|
|
if (msg_ts_off > 2 * ftRRBseqTimeout)
|
|
goto defer;
|
|
}
|
|
|
|
msg_seq = le_to_host32(msg_both->seq);
|
|
rkh_off = rkh_seq->rx.last[rkh_seq->rx.offsetidx];
|
|
msg_off = msg_seq - rkh_off;
|
|
if (msg_off > 0xC0000000)
|
|
goto out; /* too old message, drop it */
|
|
|
|
if (msg_off <= 0x40000000) {
|
|
for (i = 0; i < rkh_seq->rx.num_last; i++) {
|
|
if (rkh_seq->rx.last[i] == msg_seq)
|
|
goto out; /* duplicate message, drop it */
|
|
}
|
|
|
|
return FT_RRB_SEQ_OK;
|
|
}
|
|
|
|
defer:
|
|
if (no_defer)
|
|
goto out;
|
|
|
|
wpa_printf(MSG_DEBUG, "FT: Possibly invalid sequence number in %s from "
|
|
MACSTR, msgtype, MAC2STR(src_addr));
|
|
|
|
return FT_RRB_SEQ_DEFER;
|
|
out:
|
|
wpa_printf(MSG_DEBUG, "FT: Invalid sequence number in %s from " MACSTR,
|
|
msgtype, MAC2STR(src_addr));
|
|
|
|
return FT_RRB_SEQ_DROP;
|
|
}
|
|
|
|
|
|
static void
|
|
wpa_ft_rrb_seq_accept(struct wpa_authenticator *wpa_auth,
|
|
struct ft_remote_seq *rkh_seq, const u8 *src_addr,
|
|
const u8 *auth, size_t auth_len,
|
|
const char *msgtype)
|
|
{
|
|
const u8 *f_seq;
|
|
size_t f_seq_len;
|
|
const struct ft_rrb_seq *msg_both;
|
|
u32 msg_seq, msg_off, min_off, rkh_off;
|
|
int minidx = 0;
|
|
unsigned int i;
|
|
|
|
RRB_GET_AUTH(FT_RRB_SEQ, seq, msgtype, sizeof(*msg_both));
|
|
msg_both = (const struct ft_rrb_seq *) f_seq;
|
|
|
|
msg_seq = le_to_host32(msg_both->seq);
|
|
|
|
if (rkh_seq->rx.num_last < FT_REMOTE_SEQ_BACKLOG) {
|
|
rkh_seq->rx.last[rkh_seq->rx.num_last] = msg_seq;
|
|
rkh_seq->rx.num_last++;
|
|
return;
|
|
}
|
|
|
|
rkh_off = rkh_seq->rx.last[rkh_seq->rx.offsetidx];
|
|
for (i = 0; i < rkh_seq->rx.num_last; i++) {
|
|
msg_off = rkh_seq->rx.last[i] - rkh_off;
|
|
min_off = rkh_seq->rx.last[minidx] - rkh_off;
|
|
if (msg_off < min_off && i != rkh_seq->rx.offsetidx)
|
|
minidx = i;
|
|
}
|
|
rkh_seq->rx.last[rkh_seq->rx.offsetidx] = msg_seq;
|
|
rkh_seq->rx.offsetidx = minidx;
|
|
|
|
return;
|
|
out:
|
|
/* RRB_GET_AUTH should never fail here as
|
|
* wpa_ft_rrb_seq_chk() verified FT_RRB_SEQ presence. */
|
|
wpa_printf(MSG_ERROR, "FT: %s() failed", __func__);
|
|
}
|
|
|
|
|
|
static int wpa_ft_new_seq(struct ft_remote_seq *rkh_seq,
|
|
struct ft_rrb_seq *f_seq)
|
|
{
|
|
struct os_reltime now;
|
|
|
|
if (os_get_reltime(&now) < 0)
|
|
return -1;
|
|
|
|
if (!rkh_seq->tx.dom) {
|
|
if (random_get_bytes((u8 *) &rkh_seq->tx.seq,
|
|
sizeof(rkh_seq->tx.seq))) {
|
|
wpa_printf(MSG_ERROR,
|
|
"FT: Failed to get random data for sequence number initialization");
|
|
rkh_seq->tx.seq = now.usec;
|
|
}
|
|
if (random_get_bytes((u8 *) &rkh_seq->tx.dom,
|
|
sizeof(rkh_seq->tx.dom))) {
|
|
wpa_printf(MSG_ERROR,
|
|
"FT: Failed to get random data for sequence number initialization");
|
|
rkh_seq->tx.dom = now.usec;
|
|
}
|
|
rkh_seq->tx.dom |= 1;
|
|
}
|
|
|
|
f_seq->dom = host_to_le32(rkh_seq->tx.dom);
|
|
f_seq->seq = host_to_le32(rkh_seq->tx.seq);
|
|
f_seq->ts = host_to_le32(now.sec);
|
|
|
|
rkh_seq->tx.seq++;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
struct wpa_ft_pmk_r0_sa {
|
|
struct dl_list list;
|
|
u8 pmk_r0[PMK_LEN_MAX];
|
|
size_t pmk_r0_len;
|
|
u8 pmk_r0_name[WPA_PMK_NAME_LEN];
|
|
u8 spa[ETH_ALEN];
|
|
int pairwise; /* Pairwise cipher suite, WPA_CIPHER_* */
|
|
struct vlan_description *vlan;
|
|
os_time_t expiration; /* 0 for no expiration */
|
|
u8 *identity;
|
|
size_t identity_len;
|
|
u8 *radius_cui;
|
|
size_t radius_cui_len;
|
|
os_time_t session_timeout; /* 0 for no expiration */
|
|
/* TODO: radius_class, EAP type */
|
|
int pmk_r1_pushed;
|
|
};
|
|
|
|
struct wpa_ft_pmk_r1_sa {
|
|
struct dl_list list;
|
|
u8 pmk_r1[PMK_LEN_MAX];
|
|
size_t pmk_r1_len;
|
|
u8 pmk_r1_name[WPA_PMK_NAME_LEN];
|
|
u8 spa[ETH_ALEN];
|
|
int pairwise; /* Pairwise cipher suite, WPA_CIPHER_* */
|
|
struct vlan_description *vlan;
|
|
u8 *identity;
|
|
size_t identity_len;
|
|
u8 *radius_cui;
|
|
size_t radius_cui_len;
|
|
os_time_t session_timeout; /* 0 for no expiration */
|
|
/* TODO: radius_class, EAP type */
|
|
};
|
|
|
|
struct wpa_ft_pmk_cache {
|
|
struct dl_list pmk_r0; /* struct wpa_ft_pmk_r0_sa */
|
|
struct dl_list pmk_r1; /* struct wpa_ft_pmk_r1_sa */
|
|
};
|
|
|
|
|
|
static void wpa_ft_expire_pmk_r0(void *eloop_ctx, void *timeout_ctx);
|
|
static void wpa_ft_expire_pmk_r1(void *eloop_ctx, void *timeout_ctx);
|
|
|
|
|
|
static void wpa_ft_free_pmk_r0(struct wpa_ft_pmk_r0_sa *r0)
|
|
{
|
|
if (!r0)
|
|
return;
|
|
|
|
dl_list_del(&r0->list);
|
|
eloop_cancel_timeout(wpa_ft_expire_pmk_r0, r0, NULL);
|
|
|
|
os_memset(r0->pmk_r0, 0, PMK_LEN_MAX);
|
|
os_free(r0->vlan);
|
|
os_free(r0->identity);
|
|
os_free(r0->radius_cui);
|
|
os_free(r0);
|
|
}
|
|
|
|
|
|
static void wpa_ft_expire_pmk_r0(void *eloop_ctx, void *timeout_ctx)
|
|
{
|
|
struct wpa_ft_pmk_r0_sa *r0 = eloop_ctx;
|
|
struct os_reltime now;
|
|
int expires_in;
|
|
int session_timeout;
|
|
|
|
os_get_reltime(&now);
|
|
|
|
if (!r0)
|
|
return;
|
|
|
|
expires_in = r0->expiration - now.sec;
|
|
session_timeout = r0->session_timeout - now.sec;
|
|
/* conditions to remove from cache:
|
|
* a) r0->expiration is set and hit
|
|
* -or-
|
|
* b) r0->session_timeout is set and hit
|
|
*/
|
|
if ((!r0->expiration || expires_in > 0) &&
|
|
(!r0->session_timeout || session_timeout > 0)) {
|
|
wpa_printf(MSG_ERROR,
|
|
"FT: %s() called for non-expired entry %p",
|
|
__func__, r0);
|
|
eloop_cancel_timeout(wpa_ft_expire_pmk_r0, r0, NULL);
|
|
if (r0->expiration && expires_in > 0)
|
|
eloop_register_timeout(expires_in + 1, 0,
|
|
wpa_ft_expire_pmk_r0, r0, NULL);
|
|
if (r0->session_timeout && session_timeout > 0)
|
|
eloop_register_timeout(session_timeout + 1, 0,
|
|
wpa_ft_expire_pmk_r0, r0, NULL);
|
|
return;
|
|
}
|
|
|
|
wpa_ft_free_pmk_r0(r0);
|
|
}
|
|
|
|
|
|
static void wpa_ft_free_pmk_r1(struct wpa_ft_pmk_r1_sa *r1)
|
|
{
|
|
if (!r1)
|
|
return;
|
|
|
|
dl_list_del(&r1->list);
|
|
eloop_cancel_timeout(wpa_ft_expire_pmk_r1, r1, NULL);
|
|
|
|
os_memset(r1->pmk_r1, 0, PMK_LEN_MAX);
|
|
os_free(r1->vlan);
|
|
os_free(r1->identity);
|
|
os_free(r1->radius_cui);
|
|
os_free(r1);
|
|
}
|
|
|
|
|
|
static void wpa_ft_expire_pmk_r1(void *eloop_ctx, void *timeout_ctx)
|
|
{
|
|
struct wpa_ft_pmk_r1_sa *r1 = eloop_ctx;
|
|
|
|
wpa_ft_free_pmk_r1(r1);
|
|
}
|
|
|
|
|
|
struct wpa_ft_pmk_cache * wpa_ft_pmk_cache_init(void)
|
|
{
|
|
struct wpa_ft_pmk_cache *cache;
|
|
|
|
cache = os_zalloc(sizeof(*cache));
|
|
if (cache) {
|
|
dl_list_init(&cache->pmk_r0);
|
|
dl_list_init(&cache->pmk_r1);
|
|
}
|
|
|
|
return cache;
|
|
}
|
|
|
|
|
|
void wpa_ft_pmk_cache_deinit(struct wpa_ft_pmk_cache *cache)
|
|
{
|
|
struct wpa_ft_pmk_r0_sa *r0, *r0prev;
|
|
struct wpa_ft_pmk_r1_sa *r1, *r1prev;
|
|
|
|
dl_list_for_each_safe(r0, r0prev, &cache->pmk_r0,
|
|
struct wpa_ft_pmk_r0_sa, list)
|
|
wpa_ft_free_pmk_r0(r0);
|
|
|
|
dl_list_for_each_safe(r1, r1prev, &cache->pmk_r1,
|
|
struct wpa_ft_pmk_r1_sa, list)
|
|
wpa_ft_free_pmk_r1(r1);
|
|
|
|
os_free(cache);
|
|
}
|
|
|
|
|
|
static int wpa_ft_store_pmk_r0(struct wpa_authenticator *wpa_auth,
|
|
const u8 *spa, const u8 *pmk_r0,
|
|
size_t pmk_r0_len,
|
|
const u8 *pmk_r0_name, int pairwise,
|
|
const struct vlan_description *vlan,
|
|
int expires_in, int session_timeout,
|
|
const u8 *identity, size_t identity_len,
|
|
const u8 *radius_cui, size_t radius_cui_len)
|
|
{
|
|
struct wpa_ft_pmk_cache *cache = wpa_auth->ft_pmk_cache;
|
|
struct wpa_ft_pmk_r0_sa *r0;
|
|
struct os_reltime now;
|
|
|
|
/* TODO: add limit on number of entries in cache */
|
|
os_get_reltime(&now);
|
|
|
|
r0 = os_zalloc(sizeof(*r0));
|
|
if (r0 == NULL)
|
|
return -1;
|
|
|
|
os_memcpy(r0->pmk_r0, pmk_r0, pmk_r0_len);
|
|
r0->pmk_r0_len = pmk_r0_len;
|
|
os_memcpy(r0->pmk_r0_name, pmk_r0_name, WPA_PMK_NAME_LEN);
|
|
os_memcpy(r0->spa, spa, ETH_ALEN);
|
|
r0->pairwise = pairwise;
|
|
if (expires_in > 0)
|
|
r0->expiration = now.sec + expires_in;
|
|
if (vlan && vlan->notempty) {
|
|
r0->vlan = os_zalloc(sizeof(*vlan));
|
|
if (!r0->vlan) {
|
|
bin_clear_free(r0, sizeof(*r0));
|
|
return -1;
|
|
}
|
|
*r0->vlan = *vlan;
|
|
}
|
|
if (identity) {
|
|
r0->identity = os_malloc(identity_len);
|
|
if (r0->identity) {
|
|
os_memcpy(r0->identity, identity, identity_len);
|
|
r0->identity_len = identity_len;
|
|
}
|
|
}
|
|
if (radius_cui) {
|
|
r0->radius_cui = os_malloc(radius_cui_len);
|
|
if (r0->radius_cui) {
|
|
os_memcpy(r0->radius_cui, radius_cui, radius_cui_len);
|
|
r0->radius_cui_len = radius_cui_len;
|
|
}
|
|
}
|
|
if (session_timeout > 0)
|
|
r0->session_timeout = now.sec + session_timeout;
|
|
|
|
dl_list_add(&cache->pmk_r0, &r0->list);
|
|
if (expires_in > 0)
|
|
eloop_register_timeout(expires_in + 1, 0, wpa_ft_expire_pmk_r0,
|
|
r0, NULL);
|
|
if (session_timeout > 0)
|
|
eloop_register_timeout(session_timeout + 1, 0,
|
|
wpa_ft_expire_pmk_r0, r0, NULL);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int wpa_ft_fetch_pmk_r0(struct wpa_authenticator *wpa_auth,
|
|
const u8 *spa, const u8 *pmk_r0_name,
|
|
const struct wpa_ft_pmk_r0_sa **r0_out)
|
|
{
|
|
struct wpa_ft_pmk_cache *cache = wpa_auth->ft_pmk_cache;
|
|
struct wpa_ft_pmk_r0_sa *r0;
|
|
struct os_reltime now;
|
|
|
|
os_get_reltime(&now);
|
|
dl_list_for_each(r0, &cache->pmk_r0, struct wpa_ft_pmk_r0_sa, list) {
|
|
if (os_memcmp(r0->spa, spa, ETH_ALEN) == 0 &&
|
|
os_memcmp_const(r0->pmk_r0_name, pmk_r0_name,
|
|
WPA_PMK_NAME_LEN) == 0) {
|
|
*r0_out = r0;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
*r0_out = NULL;
|
|
return -1;
|
|
}
|
|
|
|
|
|
static int wpa_ft_store_pmk_r1(struct wpa_authenticator *wpa_auth,
|
|
const u8 *spa, const u8 *pmk_r1,
|
|
size_t pmk_r1_len,
|
|
const u8 *pmk_r1_name, int pairwise,
|
|
const struct vlan_description *vlan,
|
|
int expires_in, int session_timeout,
|
|
const u8 *identity, size_t identity_len,
|
|
const u8 *radius_cui, size_t radius_cui_len)
|
|
{
|
|
struct wpa_ft_pmk_cache *cache = wpa_auth->ft_pmk_cache;
|
|
int max_expires_in = wpa_auth->conf.r1_max_key_lifetime;
|
|
struct wpa_ft_pmk_r1_sa *r1;
|
|
struct os_reltime now;
|
|
|
|
/* TODO: limit on number of entries in cache */
|
|
os_get_reltime(&now);
|
|
|
|
if (max_expires_in && (max_expires_in < expires_in || expires_in == 0))
|
|
expires_in = max_expires_in;
|
|
|
|
r1 = os_zalloc(sizeof(*r1));
|
|
if (r1 == NULL)
|
|
return -1;
|
|
|
|
os_memcpy(r1->pmk_r1, pmk_r1, pmk_r1_len);
|
|
r1->pmk_r1_len = pmk_r1_len;
|
|
os_memcpy(r1->pmk_r1_name, pmk_r1_name, WPA_PMK_NAME_LEN);
|
|
os_memcpy(r1->spa, spa, ETH_ALEN);
|
|
r1->pairwise = pairwise;
|
|
if (vlan && vlan->notempty) {
|
|
r1->vlan = os_zalloc(sizeof(*vlan));
|
|
if (!r1->vlan) {
|
|
bin_clear_free(r1, sizeof(*r1));
|
|
return -1;
|
|
}
|
|
*r1->vlan = *vlan;
|
|
}
|
|
if (identity) {
|
|
r1->identity = os_malloc(identity_len);
|
|
if (r1->identity) {
|
|
os_memcpy(r1->identity, identity, identity_len);
|
|
r1->identity_len = identity_len;
|
|
}
|
|
}
|
|
if (radius_cui) {
|
|
r1->radius_cui = os_malloc(radius_cui_len);
|
|
if (r1->radius_cui) {
|
|
os_memcpy(r1->radius_cui, radius_cui, radius_cui_len);
|
|
r1->radius_cui_len = radius_cui_len;
|
|
}
|
|
}
|
|
if (session_timeout > 0)
|
|
r1->session_timeout = now.sec + session_timeout;
|
|
|
|
dl_list_add(&cache->pmk_r1, &r1->list);
|
|
|
|
if (expires_in > 0)
|
|
eloop_register_timeout(expires_in + 1, 0, wpa_ft_expire_pmk_r1,
|
|
r1, NULL);
|
|
if (session_timeout > 0)
|
|
eloop_register_timeout(session_timeout + 1, 0,
|
|
wpa_ft_expire_pmk_r1, r1, NULL);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int wpa_ft_fetch_pmk_r1(struct wpa_authenticator *wpa_auth,
|
|
const u8 *spa, const u8 *pmk_r1_name,
|
|
u8 *pmk_r1, size_t *pmk_r1_len, int *pairwise,
|
|
struct vlan_description *vlan,
|
|
const u8 **identity, size_t *identity_len,
|
|
const u8 **radius_cui, size_t *radius_cui_len,
|
|
int *session_timeout)
|
|
{
|
|
struct wpa_ft_pmk_cache *cache = wpa_auth->ft_pmk_cache;
|
|
struct wpa_ft_pmk_r1_sa *r1;
|
|
struct os_reltime now;
|
|
|
|
os_get_reltime(&now);
|
|
|
|
dl_list_for_each(r1, &cache->pmk_r1, struct wpa_ft_pmk_r1_sa, list) {
|
|
if (os_memcmp(r1->spa, spa, ETH_ALEN) == 0 &&
|
|
os_memcmp_const(r1->pmk_r1_name, pmk_r1_name,
|
|
WPA_PMK_NAME_LEN) == 0) {
|
|
os_memcpy(pmk_r1, r1->pmk_r1, r1->pmk_r1_len);
|
|
*pmk_r1_len = r1->pmk_r1_len;
|
|
if (pairwise)
|
|
*pairwise = r1->pairwise;
|
|
if (vlan && r1->vlan)
|
|
*vlan = *r1->vlan;
|
|
if (vlan && !r1->vlan)
|
|
os_memset(vlan, 0, sizeof(*vlan));
|
|
if (identity && identity_len) {
|
|
*identity = r1->identity;
|
|
*identity_len = r1->identity_len;
|
|
}
|
|
if (radius_cui && radius_cui_len) {
|
|
*radius_cui = r1->radius_cui;
|
|
*radius_cui_len = r1->radius_cui_len;
|
|
}
|
|
if (session_timeout && r1->session_timeout > now.sec)
|
|
*session_timeout = r1->session_timeout -
|
|
now.sec;
|
|
else if (session_timeout && r1->session_timeout)
|
|
*session_timeout = 1;
|
|
else if (session_timeout)
|
|
*session_timeout = 0;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
|
|
static int wpa_ft_rrb_init_r0kh_seq(struct ft_remote_r0kh *r0kh)
|
|
{
|
|
if (r0kh->seq)
|
|
return 0;
|
|
|
|
r0kh->seq = os_zalloc(sizeof(*r0kh->seq));
|
|
if (!r0kh->seq) {
|
|
wpa_printf(MSG_DEBUG, "FT: Failed to allocate r0kh->seq");
|
|
return -1;
|
|
}
|
|
|
|
dl_list_init(&r0kh->seq->rx.queue);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static void wpa_ft_rrb_lookup_r0kh(struct wpa_authenticator *wpa_auth,
|
|
const u8 *f_r0kh_id, size_t f_r0kh_id_len,
|
|
struct ft_remote_r0kh **r0kh_out,
|
|
struct ft_remote_r0kh **r0kh_wildcard)
|
|
{
|
|
struct ft_remote_r0kh *r0kh;
|
|
|
|
*r0kh_wildcard = NULL;
|
|
*r0kh_out = NULL;
|
|
|
|
if (wpa_auth->conf.r0kh_list)
|
|
r0kh = *wpa_auth->conf.r0kh_list;
|
|
else
|
|
r0kh = NULL;
|
|
for (; r0kh; r0kh = r0kh->next) {
|
|
if (r0kh->id_len == 1 && r0kh->id[0] == '*')
|
|
*r0kh_wildcard = r0kh;
|
|
if (f_r0kh_id && r0kh->id_len == f_r0kh_id_len &&
|
|
os_memcmp_const(f_r0kh_id, r0kh->id, f_r0kh_id_len) == 0)
|
|
*r0kh_out = r0kh;
|
|
}
|
|
|
|
if (!*r0kh_out && !*r0kh_wildcard)
|
|
wpa_printf(MSG_DEBUG, "FT: No matching R0KH found");
|
|
|
|
if (*r0kh_out && wpa_ft_rrb_init_r0kh_seq(*r0kh_out) < 0)
|
|
*r0kh_out = NULL;
|
|
}
|
|
|
|
|
|
static int wpa_ft_rrb_init_r1kh_seq(struct ft_remote_r1kh *r1kh)
|
|
{
|
|
if (r1kh->seq)
|
|
return 0;
|
|
|
|
r1kh->seq = os_zalloc(sizeof(*r1kh->seq));
|
|
if (!r1kh->seq) {
|
|
wpa_printf(MSG_DEBUG, "FT: Failed to allocate r1kh->seq");
|
|
return -1;
|
|
}
|
|
|
|
dl_list_init(&r1kh->seq->rx.queue);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static void wpa_ft_rrb_lookup_r1kh(struct wpa_authenticator *wpa_auth,
|
|
const u8 *f_r1kh_id,
|
|
struct ft_remote_r1kh **r1kh_out,
|
|
struct ft_remote_r1kh **r1kh_wildcard)
|
|
{
|
|
struct ft_remote_r1kh *r1kh;
|
|
|
|
*r1kh_wildcard = NULL;
|
|
*r1kh_out = NULL;
|
|
|
|
if (wpa_auth->conf.r1kh_list)
|
|
r1kh = *wpa_auth->conf.r1kh_list;
|
|
else
|
|
r1kh = NULL;
|
|
for (; r1kh; r1kh = r1kh->next) {
|
|
if (is_zero_ether_addr(r1kh->addr) &&
|
|
is_zero_ether_addr(r1kh->id))
|
|
*r1kh_wildcard = r1kh;
|
|
if (f_r1kh_id &&
|
|
os_memcmp_const(r1kh->id, f_r1kh_id, FT_R1KH_ID_LEN) == 0)
|
|
*r1kh_out = r1kh;
|
|
}
|
|
|
|
if (!*r1kh_out && !*r1kh_wildcard)
|
|
wpa_printf(MSG_DEBUG, "FT: No matching R1KH found");
|
|
|
|
if (*r1kh_out && wpa_ft_rrb_init_r1kh_seq(*r1kh_out) < 0)
|
|
*r1kh_out = NULL;
|
|
}
|
|
|
|
|
|
static int wpa_ft_rrb_check_r0kh(struct wpa_authenticator *wpa_auth,
|
|
const u8 *f_r0kh_id, size_t f_r0kh_id_len)
|
|
{
|
|
if (f_r0kh_id_len != wpa_auth->conf.r0_key_holder_len ||
|
|
os_memcmp_const(f_r0kh_id, wpa_auth->conf.r0_key_holder,
|
|
f_r0kh_id_len) != 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int wpa_ft_rrb_check_r1kh(struct wpa_authenticator *wpa_auth,
|
|
const u8 *f_r1kh_id)
|
|
{
|
|
if (os_memcmp_const(f_r1kh_id, wpa_auth->conf.r1_key_holder,
|
|
FT_R1KH_ID_LEN) != 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static void wpa_ft_rrb_del_r0kh(void *eloop_ctx, void *timeout_ctx)
|
|
{
|
|
struct wpa_authenticator *wpa_auth = eloop_ctx;
|
|
struct ft_remote_r0kh *r0kh, *prev = NULL;
|
|
|
|
if (!wpa_auth->conf.r0kh_list)
|
|
return;
|
|
|
|
for (r0kh = *wpa_auth->conf.r0kh_list; r0kh; r0kh = r0kh->next) {
|
|
if (r0kh == timeout_ctx)
|
|
break;
|
|
prev = r0kh;
|
|
}
|
|
if (!r0kh)
|
|
return;
|
|
if (prev)
|
|
prev->next = r0kh->next;
|
|
else
|
|
*wpa_auth->conf.r0kh_list = r0kh->next;
|
|
if (r0kh->seq)
|
|
wpa_ft_rrb_seq_flush(wpa_auth, r0kh->seq, 0);
|
|
os_free(r0kh->seq);
|
|
os_free(r0kh);
|
|
}
|
|
|
|
|
|
static void wpa_ft_rrb_r0kh_replenish(struct wpa_authenticator *wpa_auth,
|
|
struct ft_remote_r0kh *r0kh, int timeout)
|
|
{
|
|
if (timeout > 0)
|
|
eloop_replenish_timeout(timeout, 0, wpa_ft_rrb_del_r0kh,
|
|
wpa_auth, r0kh);
|
|
}
|
|
|
|
|
|
static void wpa_ft_rrb_r0kh_timeout(struct wpa_authenticator *wpa_auth,
|
|
struct ft_remote_r0kh *r0kh, int timeout)
|
|
{
|
|
eloop_cancel_timeout(wpa_ft_rrb_del_r0kh, wpa_auth, r0kh);
|
|
|
|
if (timeout > 0)
|
|
eloop_register_timeout(timeout, 0, wpa_ft_rrb_del_r0kh,
|
|
wpa_auth, r0kh);
|
|
}
|
|
|
|
|
|
static struct ft_remote_r0kh *
|
|
wpa_ft_rrb_add_r0kh(struct wpa_authenticator *wpa_auth,
|
|
struct ft_remote_r0kh *r0kh_wildcard,
|
|
const u8 *src_addr, const u8 *r0kh_id, size_t id_len,
|
|
int timeout)
|
|
{
|
|
struct ft_remote_r0kh *r0kh;
|
|
|
|
if (!wpa_auth->conf.r0kh_list)
|
|
return NULL;
|
|
|
|
r0kh = os_zalloc(sizeof(*r0kh));
|
|
if (!r0kh)
|
|
return NULL;
|
|
|
|
if (src_addr)
|
|
os_memcpy(r0kh->addr, src_addr, sizeof(r0kh->addr));
|
|
|
|
if (id_len > FT_R0KH_ID_MAX_LEN)
|
|
id_len = FT_R0KH_ID_MAX_LEN;
|
|
os_memcpy(r0kh->id, r0kh_id, id_len);
|
|
r0kh->id_len = id_len;
|
|
|
|
os_memcpy(r0kh->key, r0kh_wildcard->key, sizeof(r0kh->key));
|
|
|
|
r0kh->next = *wpa_auth->conf.r0kh_list;
|
|
*wpa_auth->conf.r0kh_list = r0kh;
|
|
|
|
if (timeout > 0)
|
|
eloop_register_timeout(timeout, 0, wpa_ft_rrb_del_r0kh,
|
|
wpa_auth, r0kh);
|
|
|
|
if (wpa_ft_rrb_init_r0kh_seq(r0kh) < 0)
|
|
return NULL;
|
|
|
|
return r0kh;
|
|
}
|
|
|
|
|
|
static void wpa_ft_rrb_del_r1kh(void *eloop_ctx, void *timeout_ctx)
|
|
{
|
|
struct wpa_authenticator *wpa_auth = eloop_ctx;
|
|
struct ft_remote_r1kh *r1kh, *prev = NULL;
|
|
|
|
if (!wpa_auth->conf.r1kh_list)
|
|
return;
|
|
|
|
for (r1kh = *wpa_auth->conf.r1kh_list; r1kh; r1kh = r1kh->next) {
|
|
if (r1kh == timeout_ctx)
|
|
break;
|
|
prev = r1kh;
|
|
}
|
|
if (!r1kh)
|
|
return;
|
|
if (prev)
|
|
prev->next = r1kh->next;
|
|
else
|
|
*wpa_auth->conf.r1kh_list = r1kh->next;
|
|
if (r1kh->seq)
|
|
wpa_ft_rrb_seq_flush(wpa_auth, r1kh->seq, 0);
|
|
os_free(r1kh->seq);
|
|
os_free(r1kh);
|
|
}
|
|
|
|
|
|
static void wpa_ft_rrb_r1kh_replenish(struct wpa_authenticator *wpa_auth,
|
|
struct ft_remote_r1kh *r1kh, int timeout)
|
|
{
|
|
if (timeout > 0)
|
|
eloop_replenish_timeout(timeout, 0, wpa_ft_rrb_del_r1kh,
|
|
wpa_auth, r1kh);
|
|
}
|
|
|
|
|
|
static struct ft_remote_r1kh *
|
|
wpa_ft_rrb_add_r1kh(struct wpa_authenticator *wpa_auth,
|
|
struct ft_remote_r1kh *r1kh_wildcard,
|
|
const u8 *src_addr, const u8 *r1kh_id, int timeout)
|
|
{
|
|
struct ft_remote_r1kh *r1kh;
|
|
|
|
if (!wpa_auth->conf.r1kh_list)
|
|
return NULL;
|
|
|
|
r1kh = os_zalloc(sizeof(*r1kh));
|
|
if (!r1kh)
|
|
return NULL;
|
|
|
|
os_memcpy(r1kh->addr, src_addr, sizeof(r1kh->addr));
|
|
os_memcpy(r1kh->id, r1kh_id, sizeof(r1kh->id));
|
|
os_memcpy(r1kh->key, r1kh_wildcard->key, sizeof(r1kh->key));
|
|
r1kh->next = *wpa_auth->conf.r1kh_list;
|
|
*wpa_auth->conf.r1kh_list = r1kh;
|
|
|
|
if (timeout > 0)
|
|
eloop_register_timeout(timeout, 0, wpa_ft_rrb_del_r1kh,
|
|
wpa_auth, r1kh);
|
|
|
|
if (wpa_ft_rrb_init_r1kh_seq(r1kh) < 0)
|
|
return NULL;
|
|
|
|
return r1kh;
|
|
}
|
|
|
|
|
|
void wpa_ft_sta_deinit(struct wpa_state_machine *sm)
|
|
{
|
|
eloop_cancel_timeout(wpa_ft_expire_pull, sm, NULL);
|
|
}
|
|
|
|
|
|
static void wpa_ft_deinit_seq(struct wpa_authenticator *wpa_auth)
|
|
{
|
|
struct ft_remote_r0kh *r0kh;
|
|
struct ft_remote_r1kh *r1kh;
|
|
|
|
eloop_cancel_timeout(wpa_ft_rrb_seq_timeout, wpa_auth, ELOOP_ALL_CTX);
|
|
|
|
if (wpa_auth->conf.r0kh_list)
|
|
r0kh = *wpa_auth->conf.r0kh_list;
|
|
else
|
|
r0kh = NULL;
|
|
for (; r0kh; r0kh = r0kh->next) {
|
|
if (!r0kh->seq)
|
|
continue;
|
|
wpa_ft_rrb_seq_flush(wpa_auth, r0kh->seq, 0);
|
|
os_free(r0kh->seq);
|
|
r0kh->seq = NULL;
|
|
}
|
|
|
|
if (wpa_auth->conf.r1kh_list)
|
|
r1kh = *wpa_auth->conf.r1kh_list;
|
|
else
|
|
r1kh = NULL;
|
|
for (; r1kh; r1kh = r1kh->next) {
|
|
if (!r1kh->seq)
|
|
continue;
|
|
wpa_ft_rrb_seq_flush(wpa_auth, r1kh->seq, 0);
|
|
os_free(r1kh->seq);
|
|
r1kh->seq = NULL;
|
|
}
|
|
}
|
|
|
|
|
|
static void wpa_ft_deinit_rkh_tmp(struct wpa_authenticator *wpa_auth)
|
|
{
|
|
struct ft_remote_r0kh *r0kh, *r0kh_next, *r0kh_prev = NULL;
|
|
struct ft_remote_r1kh *r1kh, *r1kh_next, *r1kh_prev = NULL;
|
|
|
|
if (wpa_auth->conf.r0kh_list)
|
|
r0kh = *wpa_auth->conf.r0kh_list;
|
|
else
|
|
r0kh = NULL;
|
|
while (r0kh) {
|
|
r0kh_next = r0kh->next;
|
|
if (eloop_cancel_timeout(wpa_ft_rrb_del_r0kh, wpa_auth,
|
|
r0kh) > 0) {
|
|
if (r0kh_prev)
|
|
r0kh_prev->next = r0kh_next;
|
|
else
|
|
*wpa_auth->conf.r0kh_list = r0kh_next;
|
|
os_free(r0kh);
|
|
} else {
|
|
r0kh_prev = r0kh;
|
|
}
|
|
r0kh = r0kh_next;
|
|
}
|
|
|
|
if (wpa_auth->conf.r1kh_list)
|
|
r1kh = *wpa_auth->conf.r1kh_list;
|
|
else
|
|
r1kh = NULL;
|
|
while (r1kh) {
|
|
r1kh_next = r1kh->next;
|
|
if (eloop_cancel_timeout(wpa_ft_rrb_del_r1kh, wpa_auth,
|
|
r1kh) > 0) {
|
|
if (r1kh_prev)
|
|
r1kh_prev->next = r1kh_next;
|
|
else
|
|
*wpa_auth->conf.r1kh_list = r1kh_next;
|
|
os_free(r1kh);
|
|
} else {
|
|
r1kh_prev = r1kh;
|
|
}
|
|
r1kh = r1kh_next;
|
|
}
|
|
}
|
|
|
|
|
|
void wpa_ft_deinit(struct wpa_authenticator *wpa_auth)
|
|
{
|
|
wpa_ft_deinit_seq(wpa_auth);
|
|
wpa_ft_deinit_rkh_tmp(wpa_auth);
|
|
}
|
|
|
|
|
|
static void wpa_ft_block_r0kh(struct wpa_authenticator *wpa_auth,
|
|
const u8 *f_r0kh_id, size_t f_r0kh_id_len)
|
|
{
|
|
struct ft_remote_r0kh *r0kh, *r0kh_wildcard;
|
|
|
|
if (!wpa_auth->conf.rkh_neg_timeout)
|
|
return;
|
|
|
|
wpa_ft_rrb_lookup_r0kh(wpa_auth, f_r0kh_id, f_r0kh_id_len,
|
|
&r0kh, &r0kh_wildcard);
|
|
|
|
if (!r0kh_wildcard) {
|
|
/* r0kh removed after neg_timeout and might need re-adding */
|
|
return;
|
|
}
|
|
|
|
wpa_hexdump(MSG_DEBUG, "FT: Temporarily block R0KH-ID",
|
|
f_r0kh_id, f_r0kh_id_len);
|
|
|
|
if (r0kh) {
|
|
wpa_ft_rrb_r0kh_timeout(wpa_auth, r0kh,
|
|
wpa_auth->conf.rkh_neg_timeout);
|
|
os_memset(r0kh->addr, 0, ETH_ALEN);
|
|
} else
|
|
wpa_ft_rrb_add_r0kh(wpa_auth, r0kh_wildcard, NULL, f_r0kh_id,
|
|
f_r0kh_id_len,
|
|
wpa_auth->conf.rkh_neg_timeout);
|
|
}
|
|
|
|
|
|
static void wpa_ft_expire_pull(void *eloop_ctx, void *timeout_ctx)
|
|
{
|
|
struct wpa_state_machine *sm = eloop_ctx;
|
|
|
|
wpa_printf(MSG_DEBUG, "FT: Timeout pending pull request for " MACSTR,
|
|
MAC2STR(sm->addr));
|
|
if (sm->ft_pending_pull_left_retries <= 0)
|
|
wpa_ft_block_r0kh(sm->wpa_auth, sm->r0kh_id, sm->r0kh_id_len);
|
|
|
|
/* cancel multiple timeouts */
|
|
eloop_cancel_timeout(wpa_ft_expire_pull, sm, NULL);
|
|
ft_finish_pull(sm);
|
|
}
|
|
|
|
|
|
static int wpa_ft_pull_pmk_r1(struct wpa_state_machine *sm,
|
|
const u8 *ies, size_t ies_len,
|
|
const u8 *pmk_r0_name)
|
|
{
|
|
struct ft_remote_r0kh *r0kh, *r0kh_wildcard;
|
|
u8 *packet = NULL;
|
|
const u8 *key, *f_r1kh_id = sm->wpa_auth->conf.r1_key_holder;
|
|
size_t packet_len, key_len;
|
|
struct ft_rrb_seq f_seq;
|
|
int tsecs, tusecs, first;
|
|
struct wpabuf *ft_pending_req_ies;
|
|
int r0kh_timeout;
|
|
struct tlv_list req_enc[] = {
|
|
{ .type = FT_RRB_PMK_R0_NAME, .len = WPA_PMK_NAME_LEN,
|
|
.data = pmk_r0_name },
|
|
{ .type = FT_RRB_S1KH_ID, .len = ETH_ALEN,
|
|
.data = sm->addr },
|
|
{ .type = FT_RRB_LAST_EMPTY, .len = 0, .data = NULL },
|
|
};
|
|
struct tlv_list req_auth[] = {
|
|
{ .type = FT_RRB_NONCE, .len = FT_RRB_NONCE_LEN,
|
|
.data = sm->ft_pending_pull_nonce },
|
|
{ .type = FT_RRB_SEQ, .len = sizeof(f_seq),
|
|
.data = (u8 *) &f_seq },
|
|
{ .type = FT_RRB_R0KH_ID, .len = sm->r0kh_id_len,
|
|
.data = sm->r0kh_id },
|
|
{ .type = FT_RRB_R1KH_ID, .len = FT_R1KH_ID_LEN,
|
|
.data = f_r1kh_id },
|
|
{ .type = FT_RRB_LAST_EMPTY, .len = 0, .data = NULL },
|
|
};
|
|
|
|
if (sm->ft_pending_pull_left_retries <= 0)
|
|
return -1;
|
|
first = sm->ft_pending_pull_left_retries ==
|
|
sm->wpa_auth->conf.rkh_pull_retries;
|
|
sm->ft_pending_pull_left_retries--;
|
|
|
|
wpa_ft_rrb_lookup_r0kh(sm->wpa_auth, sm->r0kh_id, sm->r0kh_id_len,
|
|
&r0kh, &r0kh_wildcard);
|
|
|
|
/* Keep r0kh sufficiently long in the list for seq num check */
|
|
r0kh_timeout = sm->wpa_auth->conf.rkh_pull_timeout / 1000 +
|
|
1 + ftRRBseqTimeout;
|
|
if (r0kh) {
|
|
wpa_ft_rrb_r0kh_replenish(sm->wpa_auth, r0kh, r0kh_timeout);
|
|
} else if (r0kh_wildcard) {
|
|
wpa_printf(MSG_DEBUG, "FT: Using wildcard R0KH-ID");
|
|
/* r0kh->addr: updated by SEQ_RESP and wpa_ft_expire_pull */
|
|
r0kh = wpa_ft_rrb_add_r0kh(sm->wpa_auth, r0kh_wildcard,
|
|
r0kh_wildcard->addr,
|
|
sm->r0kh_id, sm->r0kh_id_len,
|
|
r0kh_timeout);
|
|
}
|
|
if (r0kh == NULL) {
|
|
wpa_hexdump(MSG_DEBUG, "FT: Did not find R0KH-ID",
|
|
sm->r0kh_id, sm->r0kh_id_len);
|
|
return -1;
|
|
}
|
|
if (is_zero_ether_addr(r0kh->addr)) {
|
|
wpa_hexdump(MSG_DEBUG, "FT: R0KH-ID is temporarily blocked",
|
|
sm->r0kh_id, sm->r0kh_id_len);
|
|
return -1;
|
|
}
|
|
if (os_memcmp(r0kh->addr, sm->wpa_auth->addr, ETH_ALEN) == 0) {
|
|
wpa_printf(MSG_DEBUG,
|
|
"FT: R0KH-ID points to self - no matching key available");
|
|
return -1;
|
|
}
|
|
|
|
key = r0kh->key;
|
|
key_len = sizeof(r0kh->key);
|
|
|
|
if (r0kh->seq->rx.num_last == 0) {
|
|
/* A sequence request will be sent out anyway when pull
|
|
* response is received. Send it out now to avoid one RTT. */
|
|
wpa_ft_rrb_seq_req(sm->wpa_auth, r0kh->seq, r0kh->addr,
|
|
r0kh->id, r0kh->id_len, f_r1kh_id, key,
|
|
key_len, NULL, 0, NULL, 0, NULL);
|
|
}
|
|
|
|
wpa_printf(MSG_DEBUG, "FT: Send PMK-R1 pull request from " MACSTR
|
|
" to remote R0KH address " MACSTR,
|
|
MAC2STR(sm->wpa_auth->addr), MAC2STR(r0kh->addr));
|
|
|
|
if (first &&
|
|
random_get_bytes(sm->ft_pending_pull_nonce, FT_RRB_NONCE_LEN) < 0) {
|
|
wpa_printf(MSG_DEBUG, "FT: Failed to get random data for "
|
|
"nonce");
|
|
return -1;
|
|
}
|
|
|
|
if (wpa_ft_new_seq(r0kh->seq, &f_seq) < 0) {
|
|
wpa_printf(MSG_DEBUG, "FT: Failed to get seq num");
|
|
return -1;
|
|
}
|
|
|
|
if (wpa_ft_rrb_build(key, key_len, req_enc, NULL, req_auth, NULL,
|
|
sm->wpa_auth->addr, FT_PACKET_R0KH_R1KH_PULL,
|
|
&packet, &packet_len) < 0)
|
|
return -1;
|
|
|
|
ft_pending_req_ies = wpabuf_alloc_copy(ies, ies_len);
|
|
wpabuf_free(sm->ft_pending_req_ies);
|
|
sm->ft_pending_req_ies = ft_pending_req_ies;
|
|
if (!sm->ft_pending_req_ies) {
|
|
os_free(packet);
|
|
return -1;
|
|
}
|
|
|
|
tsecs = sm->wpa_auth->conf.rkh_pull_timeout / 1000;
|
|
tusecs = (sm->wpa_auth->conf.rkh_pull_timeout % 1000) * 1000;
|
|
eloop_register_timeout(tsecs, tusecs, wpa_ft_expire_pull, sm, NULL);
|
|
|
|
wpa_ft_rrb_oui_send(sm->wpa_auth, r0kh->addr, FT_PACKET_R0KH_R1KH_PULL,
|
|
packet, packet_len);
|
|
|
|
os_free(packet);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int wpa_ft_store_pmk_fils(struct wpa_state_machine *sm,
|
|
const u8 *pmk_r0, const u8 *pmk_r0_name)
|
|
{
|
|
int expires_in = sm->wpa_auth->conf.r0_key_lifetime;
|
|
struct vlan_description vlan;
|
|
const u8 *identity, *radius_cui;
|
|
size_t identity_len, radius_cui_len;
|
|
int session_timeout;
|
|
size_t pmk_r0_len = wpa_key_mgmt_sha384(sm->wpa_key_mgmt) ?
|
|
SHA384_MAC_LEN : PMK_LEN;
|
|
|
|
if (wpa_ft_get_vlan(sm->wpa_auth, sm->addr, &vlan) < 0) {
|
|
wpa_printf(MSG_DEBUG, "FT: vlan not available for STA " MACSTR,
|
|
MAC2STR(sm->addr));
|
|
return -1;
|
|
}
|
|
|
|
identity_len = wpa_ft_get_identity(sm->wpa_auth, sm->addr, &identity);
|
|
radius_cui_len = wpa_ft_get_radius_cui(sm->wpa_auth, sm->addr,
|
|
&radius_cui);
|
|
session_timeout = wpa_ft_get_session_timeout(sm->wpa_auth, sm->addr);
|
|
|
|
return wpa_ft_store_pmk_r0(sm->wpa_auth, sm->addr, pmk_r0, pmk_r0_len,
|
|
pmk_r0_name, sm->pairwise, &vlan, expires_in,
|
|
session_timeout, identity, identity_len,
|
|
radius_cui, radius_cui_len);
|
|
}
|
|
|
|
|
|
int wpa_auth_derive_ptk_ft(struct wpa_state_machine *sm, struct wpa_ptk *ptk)
|
|
{
|
|
u8 pmk_r0[PMK_LEN_MAX], pmk_r0_name[WPA_PMK_NAME_LEN];
|
|
size_t pmk_r0_len = wpa_key_mgmt_sha384(sm->wpa_key_mgmt) ?
|
|
SHA384_MAC_LEN : PMK_LEN;
|
|
size_t pmk_r1_len = pmk_r0_len;
|
|
u8 pmk_r1[PMK_LEN_MAX];
|
|
u8 ptk_name[WPA_PMK_NAME_LEN];
|
|
const u8 *mdid = sm->wpa_auth->conf.mobility_domain;
|
|
const u8 *r0kh = sm->wpa_auth->conf.r0_key_holder;
|
|
size_t r0kh_len = sm->wpa_auth->conf.r0_key_holder_len;
|
|
const u8 *r1kh = sm->wpa_auth->conf.r1_key_holder;
|
|
const u8 *ssid = sm->wpa_auth->conf.ssid;
|
|
size_t ssid_len = sm->wpa_auth->conf.ssid_len;
|
|
int psk_local = sm->wpa_auth->conf.ft_psk_generate_local;
|
|
int expires_in = sm->wpa_auth->conf.r0_key_lifetime;
|
|
struct vlan_description vlan;
|
|
const u8 *identity, *radius_cui;
|
|
size_t identity_len, radius_cui_len;
|
|
int session_timeout;
|
|
const u8 *mpmk;
|
|
size_t mpmk_len;
|
|
|
|
if (sm->xxkey_len > 0) {
|
|
mpmk = sm->xxkey;
|
|
mpmk_len = sm->xxkey_len;
|
|
} else if (sm->pmksa) {
|
|
mpmk = sm->pmksa->pmk;
|
|
mpmk_len = sm->pmksa->pmk_len;
|
|
} else {
|
|
wpa_printf(MSG_DEBUG, "FT: XXKey not available for key "
|
|
"derivation");
|
|
return -1;
|
|
}
|
|
|
|
if (wpa_ft_get_vlan(sm->wpa_auth, sm->addr, &vlan) < 0) {
|
|
wpa_printf(MSG_DEBUG, "FT: vlan not available for STA " MACSTR,
|
|
MAC2STR(sm->addr));
|
|
return -1;
|
|
}
|
|
|
|
identity_len = wpa_ft_get_identity(sm->wpa_auth, sm->addr, &identity);
|
|
radius_cui_len = wpa_ft_get_radius_cui(sm->wpa_auth, sm->addr,
|
|
&radius_cui);
|
|
session_timeout = wpa_ft_get_session_timeout(sm->wpa_auth, sm->addr);
|
|
|
|
if (wpa_derive_pmk_r0(mpmk, mpmk_len, ssid, ssid_len, mdid,
|
|
r0kh, r0kh_len, sm->addr,
|
|
pmk_r0, pmk_r0_name,
|
|
wpa_key_mgmt_sha384(sm->wpa_key_mgmt)) < 0)
|
|
return -1;
|
|
if (!psk_local || !wpa_key_mgmt_ft_psk(sm->wpa_key_mgmt))
|
|
wpa_ft_store_pmk_r0(sm->wpa_auth, sm->addr, pmk_r0, pmk_r0_len,
|
|
pmk_r0_name,
|
|
sm->pairwise, &vlan, expires_in,
|
|
session_timeout, identity, identity_len,
|
|
radius_cui, radius_cui_len);
|
|
|
|
if (wpa_derive_pmk_r1(pmk_r0, pmk_r0_len, pmk_r0_name, r1kh, sm->addr,
|
|
pmk_r1, sm->pmk_r1_name) < 0)
|
|
return -1;
|
|
if (!psk_local || !wpa_key_mgmt_ft_psk(sm->wpa_key_mgmt))
|
|
wpa_ft_store_pmk_r1(sm->wpa_auth, sm->addr, pmk_r1, pmk_r1_len,
|
|
sm->pmk_r1_name, sm->pairwise, &vlan,
|
|
expires_in, session_timeout, identity,
|
|
identity_len, radius_cui, radius_cui_len);
|
|
|
|
return wpa_pmk_r1_to_ptk(pmk_r1, pmk_r1_len, sm->SNonce, sm->ANonce,
|
|
sm->addr, sm->wpa_auth->addr, sm->pmk_r1_name,
|
|
ptk, ptk_name, sm->wpa_key_mgmt, sm->pairwise,
|
|
0);
|
|
}
|
|
|
|
|
|
static inline int wpa_auth_get_seqnum(struct wpa_authenticator *wpa_auth,
|
|
const u8 *addr, int idx, u8 *seq)
|
|
{
|
|
if (wpa_auth->cb->get_seqnum == NULL)
|
|
return -1;
|
|
return wpa_auth->cb->get_seqnum(wpa_auth->cb_ctx, addr, idx, seq);
|
|
}
|
|
|
|
|
|
static u8 * wpa_ft_gtk_subelem(struct wpa_state_machine *sm, size_t *len)
|
|
{
|
|
u8 *subelem;
|
|
struct wpa_auth_config *conf = &sm->wpa_auth->conf;
|
|
struct wpa_group *gsm = sm->group;
|
|
size_t subelem_len, pad_len;
|
|
const u8 *key;
|
|
size_t key_len;
|
|
u8 keybuf[WPA_GTK_MAX_LEN];
|
|
const u8 *kek;
|
|
size_t kek_len;
|
|
|
|
if (wpa_key_mgmt_fils(sm->wpa_key_mgmt)) {
|
|
kek = sm->PTK.kek2;
|
|
kek_len = sm->PTK.kek2_len;
|
|
} else {
|
|
kek = sm->PTK.kek;
|
|
kek_len = sm->PTK.kek_len;
|
|
}
|
|
|
|
key_len = gsm->GTK_len;
|
|
if (key_len > sizeof(keybuf))
|
|
return NULL;
|
|
|
|
/*
|
|
* Pad key for AES Key Wrap if it is not multiple of 8 bytes or is less
|
|
* than 16 bytes.
|
|
*/
|
|
pad_len = key_len % 8;
|
|
if (pad_len)
|
|
pad_len = 8 - pad_len;
|
|
if (key_len + pad_len < 16)
|
|
pad_len += 8;
|
|
if (pad_len && key_len < sizeof(keybuf)) {
|
|
os_memcpy(keybuf, gsm->GTK[gsm->GN - 1], key_len);
|
|
if (conf->disable_gtk ||
|
|
sm->wpa_key_mgmt == WPA_KEY_MGMT_OSEN) {
|
|
/*
|
|
* Provide unique random GTK to each STA to prevent use
|
|
* of GTK in the BSS.
|
|
*/
|
|
if (random_get_bytes(keybuf, key_len) < 0)
|
|
return NULL;
|
|
}
|
|
os_memset(keybuf + key_len, 0, pad_len);
|
|
keybuf[key_len] = 0xdd;
|
|
key_len += pad_len;
|
|
key = keybuf;
|
|
} else if (conf->disable_gtk || sm->wpa_key_mgmt == WPA_KEY_MGMT_OSEN) {
|
|
/*
|
|
* Provide unique random GTK to each STA to prevent use of GTK
|
|
* in the BSS.
|
|
*/
|
|
if (random_get_bytes(keybuf, key_len) < 0)
|
|
return NULL;
|
|
key = keybuf;
|
|
} else {
|
|
key = gsm->GTK[gsm->GN - 1];
|
|
}
|
|
|
|
/*
|
|
* Sub-elem ID[1] | Length[1] | Key Info[2] | Key Length[1] | RSC[8] |
|
|
* Key[5..32].
|
|
*/
|
|
subelem_len = 13 + key_len + 8;
|
|
subelem = os_zalloc(subelem_len);
|
|
if (subelem == NULL)
|
|
return NULL;
|
|
|
|
subelem[0] = FTIE_SUBELEM_GTK;
|
|
subelem[1] = 11 + key_len + 8;
|
|
/* Key ID in B0-B1 of Key Info */
|
|
WPA_PUT_LE16(&subelem[2], gsm->GN & 0x03);
|
|
subelem[4] = gsm->GTK_len;
|
|
wpa_auth_get_seqnum(sm->wpa_auth, NULL, gsm->GN, subelem + 5);
|
|
if (aes_wrap(kek, kek_len, key_len / 8, key, subelem + 13)) {
|
|
wpa_printf(MSG_DEBUG,
|
|
"FT: GTK subelem encryption failed: kek_len=%d",
|
|
(int) kek_len);
|
|
os_free(subelem);
|
|
return NULL;
|
|
}
|
|
|
|
forced_memzero(keybuf, sizeof(keybuf));
|
|
*len = subelem_len;
|
|
return subelem;
|
|
}
|
|
|
|
|
|
static u8 * wpa_ft_igtk_subelem(struct wpa_state_machine *sm, size_t *len)
|
|
{
|
|
u8 *subelem, *pos;
|
|
struct wpa_auth_config *conf = &sm->wpa_auth->conf;
|
|
struct wpa_group *gsm = sm->group;
|
|
size_t subelem_len;
|
|
const u8 *kek, *igtk;
|
|
size_t kek_len;
|
|
size_t igtk_len;
|
|
u8 dummy_igtk[WPA_IGTK_MAX_LEN];
|
|
|
|
if (wpa_key_mgmt_fils(sm->wpa_key_mgmt)) {
|
|
kek = sm->PTK.kek2;
|
|
kek_len = sm->PTK.kek2_len;
|
|
} else {
|
|
kek = sm->PTK.kek;
|
|
kek_len = sm->PTK.kek_len;
|
|
}
|
|
|
|
igtk_len = wpa_cipher_key_len(sm->wpa_auth->conf.group_mgmt_cipher);
|
|
|
|
/* Sub-elem ID[1] | Length[1] | KeyID[2] | IPN[6] | Key Length[1] |
|
|
* Key[16+8] */
|
|
subelem_len = 1 + 1 + 2 + 6 + 1 + igtk_len + 8;
|
|
subelem = os_zalloc(subelem_len);
|
|
if (subelem == NULL)
|
|
return NULL;
|
|
|
|
pos = subelem;
|
|
*pos++ = FTIE_SUBELEM_IGTK;
|
|
*pos++ = subelem_len - 2;
|
|
WPA_PUT_LE16(pos, gsm->GN_igtk);
|
|
pos += 2;
|
|
wpa_auth_get_seqnum(sm->wpa_auth, NULL, gsm->GN_igtk, pos);
|
|
pos += 6;
|
|
*pos++ = igtk_len;
|
|
igtk = gsm->IGTK[gsm->GN_igtk - 4];
|
|
if (conf->disable_gtk || sm->wpa_key_mgmt == WPA_KEY_MGMT_OSEN) {
|
|
/*
|
|
* Provide unique random IGTK to each STA to prevent use of
|
|
* IGTK in the BSS.
|
|
*/
|
|
if (random_get_bytes(dummy_igtk, igtk_len / 8) < 0) {
|
|
os_free(subelem);
|
|
return NULL;
|
|
}
|
|
igtk = dummy_igtk;
|
|
}
|
|
if (aes_wrap(kek, kek_len, igtk_len / 8, igtk, pos)) {
|
|
wpa_printf(MSG_DEBUG,
|
|
"FT: IGTK subelem encryption failed: kek_len=%d",
|
|
(int) kek_len);
|
|
os_free(subelem);
|
|
return NULL;
|
|
}
|
|
|
|
*len = subelem_len;
|
|
return subelem;
|
|
}
|
|
|
|
|
|
static u8 * wpa_ft_bigtk_subelem(struct wpa_state_machine *sm, size_t *len)
|
|
{
|
|
u8 *subelem, *pos;
|
|
struct wpa_group *gsm = sm->group;
|
|
size_t subelem_len;
|
|
const u8 *kek, *bigtk;
|
|
size_t kek_len;
|
|
size_t bigtk_len;
|
|
u8 dummy_bigtk[WPA_IGTK_MAX_LEN];
|
|
|
|
if (wpa_key_mgmt_fils(sm->wpa_key_mgmt)) {
|
|
kek = sm->PTK.kek2;
|
|
kek_len = sm->PTK.kek2_len;
|
|
} else {
|
|
kek = sm->PTK.kek;
|
|
kek_len = sm->PTK.kek_len;
|
|
}
|
|
|
|
bigtk_len = wpa_cipher_key_len(sm->wpa_auth->conf.group_mgmt_cipher);
|
|
|
|
/* Sub-elem ID[1] | Length[1] | KeyID[2] | BIPN[6] | Key Length[1] |
|
|
* Key[16+8] */
|
|
subelem_len = 1 + 1 + 2 + 6 + 1 + bigtk_len + 8;
|
|
subelem = os_zalloc(subelem_len);
|
|
if (subelem == NULL)
|
|
return NULL;
|
|
|
|
pos = subelem;
|
|
*pos++ = FTIE_SUBELEM_BIGTK;
|
|
*pos++ = subelem_len - 2;
|
|
WPA_PUT_LE16(pos, gsm->GN_bigtk);
|
|
pos += 2;
|
|
wpa_auth_get_seqnum(sm->wpa_auth, NULL, gsm->GN_bigtk, pos);
|
|
pos += 6;
|
|
*pos++ = bigtk_len;
|
|
bigtk = gsm->IGTK[gsm->GN_bigtk - 6];
|
|
if (sm->wpa_key_mgmt == WPA_KEY_MGMT_OSEN) {
|
|
/*
|
|
* Provide unique random BIGTK to each OSEN STA to prevent use
|
|
* of BIGTK in the BSS.
|
|
*/
|
|
if (random_get_bytes(dummy_bigtk, bigtk_len / 8) < 0) {
|
|
os_free(subelem);
|
|
return NULL;
|
|
}
|
|
bigtk = dummy_bigtk;
|
|
}
|
|
if (aes_wrap(kek, kek_len, bigtk_len / 8, bigtk, pos)) {
|
|
wpa_printf(MSG_DEBUG,
|
|
"FT: BIGTK subelem encryption failed: kek_len=%d",
|
|
(int) kek_len);
|
|
os_free(subelem);
|
|
return NULL;
|
|
}
|
|
|
|
*len = subelem_len;
|
|
return subelem;
|
|
}
|
|
|
|
|
|
static u8 * wpa_ft_process_rdie(struct wpa_state_machine *sm,
|
|
u8 *pos, u8 *end, u8 id, u8 descr_count,
|
|
const u8 *ies, size_t ies_len)
|
|
{
|
|
struct ieee802_11_elems parse;
|
|
struct rsn_rdie *rdie;
|
|
|
|
wpa_printf(MSG_DEBUG, "FT: Resource Request: id=%d descr_count=%d",
|
|
id, descr_count);
|
|
wpa_hexdump(MSG_MSGDUMP, "FT: Resource descriptor IE(s)",
|
|
ies, ies_len);
|
|
|
|
if (end - pos < (int) sizeof(*rdie)) {
|
|
wpa_printf(MSG_ERROR, "FT: Not enough room for response RDIE");
|
|
return pos;
|
|
}
|
|
|
|
*pos++ = WLAN_EID_RIC_DATA;
|
|
*pos++ = sizeof(*rdie);
|
|
rdie = (struct rsn_rdie *) pos;
|
|
rdie->id = id;
|
|
rdie->descr_count = 0;
|
|
rdie->status_code = host_to_le16(WLAN_STATUS_SUCCESS);
|
|
pos += sizeof(*rdie);
|
|
|
|
if (ieee802_11_parse_elems((u8 *) ies, ies_len, &parse, 1) ==
|
|
ParseFailed) {
|
|
wpa_printf(MSG_DEBUG, "FT: Failed to parse request IEs");
|
|
rdie->status_code =
|
|
host_to_le16(WLAN_STATUS_UNSPECIFIED_FAILURE);
|
|
return pos;
|
|
}
|
|
|
|
if (parse.wmm_tspec) {
|
|
struct wmm_tspec_element *tspec;
|
|
|
|
if (parse.wmm_tspec_len + 2 < (int) sizeof(*tspec)) {
|
|
wpa_printf(MSG_DEBUG, "FT: Too short WMM TSPEC IE "
|
|
"(%d)", (int) parse.wmm_tspec_len);
|
|
rdie->status_code =
|
|
host_to_le16(WLAN_STATUS_UNSPECIFIED_FAILURE);
|
|
return pos;
|
|
}
|
|
if (end - pos < (int) sizeof(*tspec)) {
|
|
wpa_printf(MSG_ERROR, "FT: Not enough room for "
|
|
"response TSPEC");
|
|
rdie->status_code =
|
|
host_to_le16(WLAN_STATUS_UNSPECIFIED_FAILURE);
|
|
return pos;
|
|
}
|
|
tspec = (struct wmm_tspec_element *) pos;
|
|
os_memcpy(tspec, parse.wmm_tspec - 2, sizeof(*tspec));
|
|
}
|
|
|
|
#ifdef NEED_AP_MLME
|
|
if (parse.wmm_tspec && sm->wpa_auth->conf.ap_mlme) {
|
|
int res;
|
|
|
|
res = wmm_process_tspec((struct wmm_tspec_element *) pos);
|
|
wpa_printf(MSG_DEBUG, "FT: ADDTS processing result: %d", res);
|
|
if (res == WMM_ADDTS_STATUS_INVALID_PARAMETERS)
|
|
rdie->status_code =
|
|
host_to_le16(WLAN_STATUS_INVALID_PARAMETERS);
|
|
else if (res == WMM_ADDTS_STATUS_REFUSED)
|
|
rdie->status_code =
|
|
host_to_le16(WLAN_STATUS_REQUEST_DECLINED);
|
|
else {
|
|
/* TSPEC accepted; include updated TSPEC in response */
|
|
rdie->descr_count = 1;
|
|
pos += sizeof(struct wmm_tspec_element);
|
|
}
|
|
return pos;
|
|
}
|
|
#endif /* NEED_AP_MLME */
|
|
|
|
if (parse.wmm_tspec && !sm->wpa_auth->conf.ap_mlme) {
|
|
int res;
|
|
|
|
res = wpa_ft_add_tspec(sm->wpa_auth, sm->addr, pos,
|
|
sizeof(struct wmm_tspec_element));
|
|
if (res >= 0) {
|
|
if (res)
|
|
rdie->status_code = host_to_le16(res);
|
|
else {
|
|
/* TSPEC accepted; include updated TSPEC in
|
|
* response */
|
|
rdie->descr_count = 1;
|
|
pos += sizeof(struct wmm_tspec_element);
|
|
}
|
|
return pos;
|
|
}
|
|
}
|
|
|
|
wpa_printf(MSG_DEBUG, "FT: No supported resource requested");
|
|
rdie->status_code = host_to_le16(WLAN_STATUS_UNSPECIFIED_FAILURE);
|
|
return pos;
|
|
}
|
|
|
|
|
|
static u8 * wpa_ft_process_ric(struct wpa_state_machine *sm, u8 *pos, u8 *end,
|
|
const u8 *ric, size_t ric_len)
|
|
{
|
|
const u8 *rpos, *start;
|
|
const struct rsn_rdie *rdie;
|
|
|
|
wpa_hexdump(MSG_MSGDUMP, "FT: RIC Request", ric, ric_len);
|
|
|
|
rpos = ric;
|
|
while (rpos + sizeof(*rdie) < ric + ric_len) {
|
|
if (rpos[0] != WLAN_EID_RIC_DATA || rpos[1] < sizeof(*rdie) ||
|
|
rpos + 2 + rpos[1] > ric + ric_len)
|
|
break;
|
|
rdie = (const struct rsn_rdie *) (rpos + 2);
|
|
rpos += 2 + rpos[1];
|
|
start = rpos;
|
|
|
|
while (rpos + 2 <= ric + ric_len &&
|
|
rpos + 2 + rpos[1] <= ric + ric_len) {
|
|
if (rpos[0] == WLAN_EID_RIC_DATA)
|
|
break;
|
|
rpos += 2 + rpos[1];
|
|
}
|
|
pos = wpa_ft_process_rdie(sm, pos, end, rdie->id,
|
|
rdie->descr_count,
|
|
start, rpos - start);
|
|
}
|
|
|
|
return pos;
|
|
}
|
|
|
|
|
|
u8 * wpa_sm_write_assoc_resp_ies(struct wpa_state_machine *sm, u8 *pos,
|
|
size_t max_len, int auth_alg,
|
|
const u8 *req_ies, size_t req_ies_len,
|
|
int omit_rsnxe)
|
|
{
|
|
u8 *end, *mdie, *ftie, *rsnie = NULL, *r0kh_id, *subelem = NULL;
|
|
u8 *fte_mic, *elem_count;
|
|
size_t mdie_len, ftie_len, rsnie_len = 0, r0kh_id_len, subelem_len = 0;
|
|
u8 rsnxe_buf[10], *rsnxe = rsnxe_buf;
|
|
size_t rsnxe_len;
|
|
int rsnxe_used;
|
|
int res;
|
|
struct wpa_auth_config *conf;
|
|
struct wpa_ft_ies parse;
|
|
u8 *ric_start;
|
|
u8 *anonce, *snonce;
|
|
const u8 *kck;
|
|
size_t kck_len;
|
|
int use_sha384;
|
|
|
|
if (sm == NULL)
|
|
return pos;
|
|
|
|
use_sha384 = wpa_key_mgmt_sha384(sm->wpa_key_mgmt);
|
|
conf = &sm->wpa_auth->conf;
|
|
|
|
if (!wpa_key_mgmt_ft(sm->wpa_key_mgmt))
|
|
return pos;
|
|
|
|
end = pos + max_len;
|
|
|
|
#ifdef CONFIG_TESTING_OPTIONS
|
|
if (auth_alg == WLAN_AUTH_FT &&
|
|
sm->wpa_auth->conf.rsne_override_ft_set) {
|
|
wpa_printf(MSG_DEBUG,
|
|
"TESTING: RSNE FT override for MIC calculation");
|
|
rsnie = sm->wpa_auth->conf.rsne_override_ft;
|
|
rsnie_len = sm->wpa_auth->conf.rsne_override_ft_len;
|
|
if (end - pos < (long int) rsnie_len)
|
|
return pos;
|
|
os_memcpy(pos, rsnie, rsnie_len);
|
|
rsnie = pos;
|
|
pos += rsnie_len;
|
|
if (rsnie_len > PMKID_LEN && sm->pmk_r1_name_valid) {
|
|
int idx;
|
|
|
|
/* Replace all 0xff PMKID with the valid PMKR1Name */
|
|
for (idx = 0; idx < PMKID_LEN; idx++) {
|
|
if (rsnie[rsnie_len - 1 - idx] != 0xff)
|
|
break;
|
|
}
|
|
if (idx == PMKID_LEN)
|
|
os_memcpy(&rsnie[rsnie_len - PMKID_LEN],
|
|
sm->pmk_r1_name, WPA_PMK_NAME_LEN);
|
|
}
|
|
} else
|
|
#endif /* CONFIG_TESTING_OPTIONS */
|
|
if (auth_alg == WLAN_AUTH_FT ||
|
|
((auth_alg == WLAN_AUTH_FILS_SK ||
|
|
auth_alg == WLAN_AUTH_FILS_SK_PFS ||
|
|
auth_alg == WLAN_AUTH_FILS_PK) &&
|
|
(sm->wpa_key_mgmt & (WPA_KEY_MGMT_FT_FILS_SHA256 |
|
|
WPA_KEY_MGMT_FT_FILS_SHA384)))) {
|
|
if (!sm->pmk_r1_name_valid) {
|
|
wpa_printf(MSG_ERROR,
|
|
"FT: PMKR1Name is not valid for Assoc Resp RSNE");
|
|
return NULL;
|
|
}
|
|
wpa_hexdump(MSG_DEBUG, "FT: PMKR1Name for Assoc Resp RSNE",
|
|
sm->pmk_r1_name, WPA_PMK_NAME_LEN);
|
|
/*
|
|
* RSN (only present if this is a Reassociation Response and
|
|
* part of a fast BSS transition; or if this is a
|
|
* (Re)Association Response frame during an FT initial mobility
|
|
* domain association using FILS)
|
|
*/
|
|
res = wpa_write_rsn_ie(conf, pos, end - pos, sm->pmk_r1_name);
|
|
if (res < 0)
|
|
return NULL;
|
|
rsnie = pos;
|
|
rsnie_len = res;
|
|
pos += res;
|
|
}
|
|
|
|
/* Mobility Domain Information */
|
|
res = wpa_write_mdie(conf, pos, end - pos);
|
|
if (res < 0)
|
|
return NULL;
|
|
mdie = pos;
|
|
mdie_len = res;
|
|
pos += res;
|
|
|
|
/* Fast BSS Transition Information */
|
|
if (auth_alg == WLAN_AUTH_FT) {
|
|
subelem = wpa_ft_gtk_subelem(sm, &subelem_len);
|
|
if (!subelem) {
|
|
wpa_printf(MSG_DEBUG,
|
|
"FT: Failed to add GTK subelement");
|
|
return NULL;
|
|
}
|
|
r0kh_id = sm->r0kh_id;
|
|
r0kh_id_len = sm->r0kh_id_len;
|
|
anonce = sm->ANonce;
|
|
snonce = sm->SNonce;
|
|
if (sm->mgmt_frame_prot) {
|
|
u8 *igtk;
|
|
size_t igtk_len;
|
|
u8 *nbuf;
|
|
igtk = wpa_ft_igtk_subelem(sm, &igtk_len);
|
|
if (igtk == NULL) {
|
|
wpa_printf(MSG_DEBUG,
|
|
"FT: Failed to add IGTK subelement");
|
|
os_free(subelem);
|
|
return NULL;
|
|
}
|
|
nbuf = os_realloc(subelem, subelem_len + igtk_len);
|
|
if (nbuf == NULL) {
|
|
os_free(subelem);
|
|
os_free(igtk);
|
|
return NULL;
|
|
}
|
|
subelem = nbuf;
|
|
os_memcpy(subelem + subelem_len, igtk, igtk_len);
|
|
subelem_len += igtk_len;
|
|
os_free(igtk);
|
|
}
|
|
if (sm->mgmt_frame_prot && conf->beacon_prot) {
|
|
u8 *bigtk;
|
|
size_t bigtk_len;
|
|
u8 *nbuf;
|
|
|
|
bigtk = wpa_ft_bigtk_subelem(sm, &bigtk_len);
|
|
if (!bigtk) {
|
|
wpa_printf(MSG_DEBUG,
|
|
"FT: Failed to add BIGTK subelement");
|
|
os_free(subelem);
|
|
return NULL;
|
|
}
|
|
nbuf = os_realloc(subelem, subelem_len + bigtk_len);
|
|
if (!nbuf) {
|
|
os_free(subelem);
|
|
os_free(bigtk);
|
|
return NULL;
|
|
}
|
|
subelem = nbuf;
|
|
os_memcpy(subelem + subelem_len, bigtk, bigtk_len);
|
|
subelem_len += bigtk_len;
|
|
os_free(bigtk);
|
|
}
|
|
#ifdef CONFIG_OCV
|
|
if (wpa_auth_uses_ocv(sm)) {
|
|
struct wpa_channel_info ci;
|
|
u8 *nbuf, *ocipos;
|
|
|
|
if (wpa_channel_info(sm->wpa_auth, &ci) != 0) {
|
|
wpa_printf(MSG_WARNING,
|
|
"Failed to get channel info for OCI element");
|
|
os_free(subelem);
|
|
return NULL;
|
|
}
|
|
#ifdef CONFIG_TESTING_OPTIONS
|
|
if (conf->oci_freq_override_ft_assoc) {
|
|
wpa_printf(MSG_INFO,
|
|
"TEST: Override OCI frequency %d -> %u MHz",
|
|
ci.frequency,
|
|
conf->oci_freq_override_ft_assoc);
|
|
ci.frequency = conf->oci_freq_override_ft_assoc;
|
|
}
|
|
#endif /* CONFIG_TESTING_OPTIONS */
|
|
|
|
subelem_len += 2 + OCV_OCI_LEN;
|
|
nbuf = os_realloc(subelem, subelem_len);
|
|
if (!nbuf) {
|
|
os_free(subelem);
|
|
return NULL;
|
|
}
|
|
subelem = nbuf;
|
|
|
|
ocipos = subelem + subelem_len - 2 - OCV_OCI_LEN;
|
|
*ocipos++ = FTIE_SUBELEM_OCI;
|
|
*ocipos++ = OCV_OCI_LEN;
|
|
if (ocv_insert_oci(&ci, &ocipos) < 0) {
|
|
os_free(subelem);
|
|
return NULL;
|
|
}
|
|
}
|
|
#endif /* CONFIG_OCV */
|
|
} else {
|
|
r0kh_id = conf->r0_key_holder;
|
|
r0kh_id_len = conf->r0_key_holder_len;
|
|
anonce = NULL;
|
|
snonce = NULL;
|
|
}
|
|
rsnxe_used = (auth_alg == WLAN_AUTH_FT) &&
|
|
(conf->sae_pwe == 1 || conf->sae_pwe == 2);
|
|
#ifdef CONFIG_TESTING_OPTIONS
|
|
if (sm->wpa_auth->conf.ft_rsnxe_used) {
|
|
rsnxe_used = sm->wpa_auth->conf.ft_rsnxe_used == 1;
|
|
wpa_printf(MSG_DEBUG, "TESTING: FT: Force RSNXE Used %d",
|
|
rsnxe_used);
|
|
}
|
|
#endif /* CONFIG_TESTING_OPTIONS */
|
|
res = wpa_write_ftie(conf, use_sha384, r0kh_id, r0kh_id_len,
|
|
anonce, snonce, pos, end - pos,
|
|
subelem, subelem_len, rsnxe_used);
|
|
os_free(subelem);
|
|
if (res < 0)
|
|
return NULL;
|
|
ftie = pos;
|
|
ftie_len = res;
|
|
pos += res;
|
|
|
|
if (use_sha384) {
|
|
struct rsn_ftie_sha384 *_ftie =
|
|
(struct rsn_ftie_sha384 *) (ftie + 2);
|
|
|
|
fte_mic = _ftie->mic;
|
|
elem_count = &_ftie->mic_control[1];
|
|
} else {
|
|
struct rsn_ftie *_ftie = (struct rsn_ftie *) (ftie + 2);
|
|
|
|
fte_mic = _ftie->mic;
|
|
elem_count = &_ftie->mic_control[1];
|
|
}
|
|
if (auth_alg == WLAN_AUTH_FT)
|
|
*elem_count = 3; /* Information element count */
|
|
|
|
ric_start = pos;
|
|
if (wpa_ft_parse_ies(req_ies, req_ies_len, &parse, use_sha384) == 0
|
|
&& parse.ric) {
|
|
pos = wpa_ft_process_ric(sm, pos, end, parse.ric,
|
|
parse.ric_len);
|
|
if (auth_alg == WLAN_AUTH_FT)
|
|
*elem_count +=
|
|
ieee802_11_ie_count(ric_start,
|
|
pos - ric_start);
|
|
}
|
|
if (ric_start == pos)
|
|
ric_start = NULL;
|
|
|
|
if (omit_rsnxe) {
|
|
rsnxe_len = 0;
|
|
} else {
|
|
res = wpa_write_rsnxe(&sm->wpa_auth->conf, rsnxe,
|
|
sizeof(rsnxe_buf));
|
|
if (res < 0)
|
|
return NULL;
|
|
rsnxe_len = res;
|
|
}
|
|
#ifdef CONFIG_TESTING_OPTIONS
|
|
if (auth_alg == WLAN_AUTH_FT &&
|
|
sm->wpa_auth->conf.rsnxe_override_ft_set) {
|
|
wpa_printf(MSG_DEBUG,
|
|
"TESTING: RSNXE FT override for MIC calculation");
|
|
rsnxe = sm->wpa_auth->conf.rsnxe_override_ft;
|
|
rsnxe_len = sm->wpa_auth->conf.rsnxe_override_ft_len;
|
|
}
|
|
#endif /* CONFIG_TESTING_OPTIONS */
|
|
if (auth_alg == WLAN_AUTH_FT && rsnxe_len)
|
|
*elem_count += 1;
|
|
|
|
if (wpa_key_mgmt_fils(sm->wpa_key_mgmt)) {
|
|
kck = sm->PTK.kck2;
|
|
kck_len = sm->PTK.kck2_len;
|
|
} else {
|
|
kck = sm->PTK.kck;
|
|
kck_len = sm->PTK.kck_len;
|
|
}
|
|
if (auth_alg == WLAN_AUTH_FT &&
|
|
wpa_ft_mic(kck, kck_len, sm->addr, sm->wpa_auth->addr, 6,
|
|
mdie, mdie_len, ftie, ftie_len,
|
|
rsnie, rsnie_len,
|
|
ric_start, ric_start ? pos - ric_start : 0,
|
|
rsnxe_len ? rsnxe : NULL, rsnxe_len,
|
|
fte_mic) < 0) {
|
|
wpa_printf(MSG_DEBUG, "FT: Failed to calculate MIC");
|
|
return NULL;
|
|
}
|
|
|
|
os_free(sm->assoc_resp_ftie);
|
|
sm->assoc_resp_ftie = os_malloc(ftie_len);
|
|
if (!sm->assoc_resp_ftie)
|
|
return NULL;
|
|
os_memcpy(sm->assoc_resp_ftie, ftie, ftie_len);
|
|
|
|
return pos;
|
|
}
|
|
|
|
|
|
static inline int wpa_auth_set_key(struct wpa_authenticator *wpa_auth,
|
|
int vlan_id,
|
|
enum wpa_alg alg, const u8 *addr, int idx,
|
|
u8 *key, size_t key_len,
|
|
enum key_flag key_flag)
|
|
{
|
|
if (wpa_auth->cb->set_key == NULL)
|
|
return -1;
|
|
return wpa_auth->cb->set_key(wpa_auth->cb_ctx, vlan_id, alg, addr, idx,
|
|
key, key_len, key_flag);
|
|
}
|
|
|
|
|
|
static inline int wpa_auth_add_sta_ft(struct wpa_authenticator *wpa_auth,
|
|
const u8 *addr)
|
|
{
|
|
if (!wpa_auth->cb->add_sta_ft)
|
|
return -1;
|
|
return wpa_auth->cb->add_sta_ft(wpa_auth->cb_ctx, addr);
|
|
}
|
|
|
|
|
|
void wpa_ft_install_ptk(struct wpa_state_machine *sm, int retry)
|
|
{
|
|
enum wpa_alg alg;
|
|
int klen;
|
|
|
|
/* MLME-SETKEYS.request(PTK) */
|
|
alg = wpa_cipher_to_alg(sm->pairwise);
|
|
klen = wpa_cipher_key_len(sm->pairwise);
|
|
if (!wpa_cipher_valid_pairwise(sm->pairwise)) {
|
|
wpa_printf(MSG_DEBUG, "FT: Unknown pairwise alg 0x%x - skip "
|
|
"PTK configuration", sm->pairwise);
|
|
return;
|
|
}
|
|
|
|
if (sm->tk_already_set) {
|
|
/* Must avoid TK reconfiguration to prevent clearing of TX/RX
|
|
* PN in the driver */
|
|
wpa_printf(MSG_DEBUG,
|
|
"FT: Do not re-install same PTK to the driver");
|
|
return;
|
|
}
|
|
|
|
if (!retry)
|
|
wpa_auth_add_sta_ft(sm->wpa_auth, sm->addr);
|
|
|
|
/* FIX: add STA entry to kernel/driver here? The set_key will fail
|
|
* most likely without this.. At the moment, STA entry is added only
|
|
* after association has been completed. This function will be called
|
|
* again after association to get the PTK configured, but that could be
|
|
* optimized by adding the STA entry earlier.
|
|
*/
|
|
if (wpa_auth_set_key(sm->wpa_auth, 0, alg, sm->addr, sm->keyidx_active,
|
|
sm->PTK.tk, klen, KEY_FLAG_PAIRWISE_RX_TX))
|
|
return;
|
|
|
|
/* FIX: MLME-SetProtection.Request(TA, Tx_Rx) */
|
|
sm->pairwise_set = true;
|
|
sm->tk_already_set = true;
|
|
}
|
|
|
|
|
|
/* Derive PMK-R1 from PSK, check all available PSK */
|
|
static int wpa_ft_psk_pmk_r1(struct wpa_state_machine *sm,
|
|
const u8 *req_pmk_r1_name,
|
|
u8 *out_pmk_r1, int *out_pairwise,
|
|
struct vlan_description *out_vlan,
|
|
const u8 **out_identity, size_t *out_identity_len,
|
|
const u8 **out_radius_cui,
|
|
size_t *out_radius_cui_len,
|
|
int *out_session_timeout)
|
|
{
|
|
const u8 *pmk = NULL;
|
|
u8 pmk_r0[PMK_LEN], pmk_r0_name[WPA_PMK_NAME_LEN];
|
|
u8 pmk_r1[PMK_LEN], pmk_r1_name[WPA_PMK_NAME_LEN];
|
|
struct wpa_authenticator *wpa_auth = sm->wpa_auth;
|
|
const u8 *mdid = wpa_auth->conf.mobility_domain;
|
|
const u8 *r0kh = sm->r0kh_id;
|
|
size_t r0kh_len = sm->r0kh_id_len;
|
|
const u8 *r1kh = wpa_auth->conf.r1_key_holder;
|
|
const u8 *ssid = wpa_auth->conf.ssid;
|
|
size_t ssid_len = wpa_auth->conf.ssid_len;
|
|
int pairwise;
|
|
|
|
pairwise = sm->pairwise;
|
|
|
|
for (;;) {
|
|
pmk = wpa_ft_get_psk(wpa_auth, sm->addr, sm->p2p_dev_addr,
|
|
pmk);
|
|
if (pmk == NULL)
|
|
break;
|
|
|
|
if (wpa_derive_pmk_r0(pmk, PMK_LEN, ssid, ssid_len, mdid, r0kh,
|
|
r0kh_len, sm->addr,
|
|
pmk_r0, pmk_r0_name, 0) < 0 ||
|
|
wpa_derive_pmk_r1(pmk_r0, PMK_LEN, pmk_r0_name, r1kh,
|
|
sm->addr, pmk_r1, pmk_r1_name) < 0 ||
|
|
os_memcmp_const(pmk_r1_name, req_pmk_r1_name,
|
|
WPA_PMK_NAME_LEN) != 0)
|
|
continue;
|
|
|
|
/* We found a PSK that matches the requested pmk_r1_name */
|
|
wpa_printf(MSG_DEBUG,
|
|
"FT: Found PSK to generate PMK-R1 locally");
|
|
os_memcpy(out_pmk_r1, pmk_r1, PMK_LEN);
|
|
if (out_pairwise)
|
|
*out_pairwise = pairwise;
|
|
os_memcpy(sm->PMK, pmk, PMK_LEN);
|
|
sm->pmk_len = PMK_LEN;
|
|
if (out_vlan &&
|
|
wpa_ft_get_vlan(sm->wpa_auth, sm->addr, out_vlan) < 0) {
|
|
wpa_printf(MSG_DEBUG, "FT: vlan not available for STA "
|
|
MACSTR, MAC2STR(sm->addr));
|
|
return -1;
|
|
}
|
|
|
|
if (out_identity && out_identity_len) {
|
|
*out_identity_len = wpa_ft_get_identity(
|
|
sm->wpa_auth, sm->addr, out_identity);
|
|
}
|
|
|
|
if (out_radius_cui && out_radius_cui_len) {
|
|
*out_radius_cui_len = wpa_ft_get_radius_cui(
|
|
sm->wpa_auth, sm->addr, out_radius_cui);
|
|
}
|
|
|
|
if (out_session_timeout) {
|
|
*out_session_timeout = wpa_ft_get_session_timeout(
|
|
sm->wpa_auth, sm->addr);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
wpa_printf(MSG_DEBUG,
|
|
"FT: Did not find PSK to generate PMK-R1 locally");
|
|
return -1;
|
|
}
|
|
|
|
|
|
/* Detect the configuration the station asked for.
|
|
* Required to detect FT-PSK and pairwise cipher.
|
|
*/
|
|
static int wpa_ft_set_key_mgmt(struct wpa_state_machine *sm,
|
|
struct wpa_ft_ies *parse)
|
|
{
|
|
int key_mgmt, ciphers;
|
|
|
|
if (sm->wpa_key_mgmt)
|
|
return 0;
|
|
|
|
key_mgmt = parse->key_mgmt & sm->wpa_auth->conf.wpa_key_mgmt;
|
|
if (!key_mgmt) {
|
|
wpa_printf(MSG_DEBUG, "FT: Invalid key mgmt (0x%x) from "
|
|
MACSTR, parse->key_mgmt, MAC2STR(sm->addr));
|
|
return -1;
|
|
}
|
|
if (key_mgmt & WPA_KEY_MGMT_FT_IEEE8021X)
|
|
sm->wpa_key_mgmt = WPA_KEY_MGMT_FT_IEEE8021X;
|
|
#ifdef CONFIG_SHA384
|
|
else if (key_mgmt & WPA_KEY_MGMT_FT_IEEE8021X_SHA384)
|
|
sm->wpa_key_mgmt = WPA_KEY_MGMT_FT_IEEE8021X_SHA384;
|
|
#endif /* CONFIG_SHA384 */
|
|
else if (key_mgmt & WPA_KEY_MGMT_FT_PSK)
|
|
sm->wpa_key_mgmt = WPA_KEY_MGMT_FT_PSK;
|
|
#ifdef CONFIG_FILS
|
|
else if (key_mgmt & WPA_KEY_MGMT_FT_FILS_SHA256)
|
|
sm->wpa_key_mgmt = WPA_KEY_MGMT_FT_FILS_SHA256;
|
|
else if (key_mgmt & WPA_KEY_MGMT_FT_FILS_SHA384)
|
|
sm->wpa_key_mgmt = WPA_KEY_MGMT_FT_FILS_SHA384;
|
|
#endif /* CONFIG_FILS */
|
|
ciphers = parse->pairwise_cipher & sm->wpa_auth->conf.rsn_pairwise;
|
|
if (!ciphers) {
|
|
wpa_printf(MSG_DEBUG, "FT: Invalid pairwise cipher (0x%x) from "
|
|
MACSTR,
|
|
parse->pairwise_cipher, MAC2STR(sm->addr));
|
|
return -1;
|
|
}
|
|
sm->pairwise = wpa_pick_pairwise_cipher(ciphers, 0);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int wpa_ft_local_derive_pmk_r1(struct wpa_authenticator *wpa_auth,
|
|
struct wpa_state_machine *sm,
|
|
const u8 *r0kh_id, size_t r0kh_id_len,
|
|
const u8 *req_pmk_r0_name,
|
|
const u8 *req_pmk_r1_name,
|
|
u8 *out_pmk_r1, int *out_pairwise,
|
|
struct vlan_description *vlan,
|
|
const u8 **identity, size_t *identity_len,
|
|
const u8 **radius_cui,
|
|
size_t *radius_cui_len,
|
|
int *out_session_timeout)
|
|
{
|
|
struct wpa_auth_config *conf = &wpa_auth->conf;
|
|
const struct wpa_ft_pmk_r0_sa *r0;
|
|
u8 pmk_r1_name[WPA_PMK_NAME_LEN];
|
|
int expires_in = 0;
|
|
int session_timeout = 0;
|
|
struct os_reltime now;
|
|
|
|
if (conf->r0_key_holder_len != r0kh_id_len ||
|
|
os_memcmp(conf->r0_key_holder, r0kh_id, conf->r0_key_holder_len) !=
|
|
0)
|
|
return -1; /* not our R0KH-ID */
|
|
|
|
wpa_printf(MSG_DEBUG, "FT: STA R0KH-ID matching local configuration");
|
|
if (wpa_ft_fetch_pmk_r0(sm->wpa_auth, sm->addr, req_pmk_r0_name, &r0) <
|
|
0)
|
|
return -1; /* no matching PMKR0Name in local cache */
|
|
|
|
wpa_printf(MSG_DEBUG, "FT: Requested PMKR0Name found in local cache");
|
|
|
|
if (wpa_derive_pmk_r1(r0->pmk_r0, r0->pmk_r0_len, r0->pmk_r0_name,
|
|
conf->r1_key_holder,
|
|
sm->addr, out_pmk_r1, pmk_r1_name) < 0)
|
|
return -1;
|
|
|
|
os_get_reltime(&now);
|
|
if (r0->expiration)
|
|
expires_in = r0->expiration - now.sec;
|
|
|
|
if (r0->session_timeout)
|
|
session_timeout = r0->session_timeout - now.sec;
|
|
|
|
wpa_ft_store_pmk_r1(wpa_auth, sm->addr, out_pmk_r1, r0->pmk_r0_len,
|
|
pmk_r1_name,
|
|
sm->pairwise, r0->vlan, expires_in, session_timeout,
|
|
r0->identity, r0->identity_len,
|
|
r0->radius_cui, r0->radius_cui_len);
|
|
|
|
*out_pairwise = sm->pairwise;
|
|
if (vlan) {
|
|
if (r0->vlan)
|
|
*vlan = *r0->vlan;
|
|
else
|
|
os_memset(vlan, 0, sizeof(*vlan));
|
|
}
|
|
|
|
if (identity && identity_len) {
|
|
*identity = r0->identity;
|
|
*identity_len = r0->identity_len;
|
|
}
|
|
|
|
if (radius_cui && radius_cui_len) {
|
|
*radius_cui = r0->radius_cui;
|
|
*radius_cui_len = r0->radius_cui_len;
|
|
}
|
|
|
|
*out_session_timeout = session_timeout;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int wpa_ft_process_auth_req(struct wpa_state_machine *sm,
|
|
const u8 *ies, size_t ies_len,
|
|
u8 **resp_ies, size_t *resp_ies_len)
|
|
{
|
|
struct rsn_mdie *mdie;
|
|
u8 pmk_r1[PMK_LEN_MAX], pmk_r1_name[WPA_PMK_NAME_LEN];
|
|
u8 ptk_name[WPA_PMK_NAME_LEN];
|
|
struct wpa_auth_config *conf;
|
|
struct wpa_ft_ies parse;
|
|
size_t buflen;
|
|
int ret;
|
|
u8 *pos, *end;
|
|
int pairwise, session_timeout = 0;
|
|
struct vlan_description vlan;
|
|
const u8 *identity, *radius_cui;
|
|
size_t identity_len = 0, radius_cui_len = 0;
|
|
int use_sha384;
|
|
size_t pmk_r1_len, kdk_len;
|
|
|
|
*resp_ies = NULL;
|
|
*resp_ies_len = 0;
|
|
|
|
sm->pmk_r1_name_valid = 0;
|
|
conf = &sm->wpa_auth->conf;
|
|
|
|
wpa_hexdump(MSG_DEBUG, "FT: Received authentication frame IEs",
|
|
ies, ies_len);
|
|
|
|
if (wpa_ft_parse_ies(ies, ies_len, &parse, -1)) {
|
|
wpa_printf(MSG_DEBUG, "FT: Failed to parse FT IEs");
|
|
return WLAN_STATUS_UNSPECIFIED_FAILURE;
|
|
}
|
|
use_sha384 = wpa_key_mgmt_sha384(parse.key_mgmt);
|
|
pmk_r1_len = use_sha384 ? SHA384_MAC_LEN : PMK_LEN;
|
|
|
|
mdie = (struct rsn_mdie *) parse.mdie;
|
|
if (mdie == NULL || parse.mdie_len < sizeof(*mdie) ||
|
|
os_memcmp(mdie->mobility_domain,
|
|
sm->wpa_auth->conf.mobility_domain,
|
|
MOBILITY_DOMAIN_ID_LEN) != 0) {
|
|
wpa_printf(MSG_DEBUG, "FT: Invalid MDIE");
|
|
return WLAN_STATUS_INVALID_MDIE;
|
|
}
|
|
|
|
if (use_sha384) {
|
|
struct rsn_ftie_sha384 *ftie;
|
|
|
|
ftie = (struct rsn_ftie_sha384 *) parse.ftie;
|
|
if (!ftie || parse.ftie_len < sizeof(*ftie)) {
|
|
wpa_printf(MSG_DEBUG, "FT: Invalid FTIE");
|
|
return WLAN_STATUS_INVALID_FTIE;
|
|
}
|
|
|
|
os_memcpy(sm->SNonce, ftie->snonce, WPA_NONCE_LEN);
|
|
} else {
|
|
struct rsn_ftie *ftie;
|
|
|
|
ftie = (struct rsn_ftie *) parse.ftie;
|
|
if (!ftie || parse.ftie_len < sizeof(*ftie)) {
|
|
wpa_printf(MSG_DEBUG, "FT: Invalid FTIE");
|
|
return WLAN_STATUS_INVALID_FTIE;
|
|
}
|
|
|
|
os_memcpy(sm->SNonce, ftie->snonce, WPA_NONCE_LEN);
|
|
}
|
|
|
|
if (parse.r0kh_id == NULL) {
|
|
wpa_printf(MSG_DEBUG, "FT: Invalid FTIE - no R0KH-ID");
|
|
return WLAN_STATUS_INVALID_FTIE;
|
|
}
|
|
|
|
wpa_hexdump(MSG_DEBUG, "FT: STA R0KH-ID",
|
|
parse.r0kh_id, parse.r0kh_id_len);
|
|
os_memcpy(sm->r0kh_id, parse.r0kh_id, parse.r0kh_id_len);
|
|
sm->r0kh_id_len = parse.r0kh_id_len;
|
|
|
|
if (parse.rsn_pmkid == NULL) {
|
|
wpa_printf(MSG_DEBUG, "FT: No PMKID in RSNIE");
|
|
return WLAN_STATUS_INVALID_PMKID;
|
|
}
|
|
|
|
if (wpa_ft_set_key_mgmt(sm, &parse) < 0)
|
|
return WLAN_STATUS_UNSPECIFIED_FAILURE;
|
|
|
|
wpa_hexdump(MSG_DEBUG, "FT: Requested PMKR0Name",
|
|
parse.rsn_pmkid, WPA_PMK_NAME_LEN);
|
|
if (wpa_derive_pmk_r1_name(parse.rsn_pmkid,
|
|
sm->wpa_auth->conf.r1_key_holder, sm->addr,
|
|
pmk_r1_name, use_sha384) < 0)
|
|
return WLAN_STATUS_UNSPECIFIED_FAILURE;
|
|
|
|
if (conf->ft_psk_generate_local &&
|
|
wpa_key_mgmt_ft_psk(sm->wpa_key_mgmt)) {
|
|
if (wpa_ft_psk_pmk_r1(sm, pmk_r1_name, pmk_r1, &pairwise,
|
|
&vlan, &identity, &identity_len,
|
|
&radius_cui, &radius_cui_len,
|
|
&session_timeout) < 0)
|
|
return WLAN_STATUS_INVALID_PMKID;
|
|
wpa_printf(MSG_DEBUG,
|
|
"FT: Generated PMK-R1 for FT-PSK locally");
|
|
} else if (wpa_ft_fetch_pmk_r1(sm->wpa_auth, sm->addr, pmk_r1_name,
|
|
pmk_r1, &pmk_r1_len, &pairwise, &vlan,
|
|
&identity, &identity_len, &radius_cui,
|
|
&radius_cui_len, &session_timeout) < 0) {
|
|
wpa_printf(MSG_DEBUG,
|
|
"FT: No PMK-R1 available in local cache for the requested PMKR1Name");
|
|
if (wpa_ft_local_derive_pmk_r1(sm->wpa_auth, sm,
|
|
parse.r0kh_id, parse.r0kh_id_len,
|
|
parse.rsn_pmkid,
|
|
pmk_r1_name, pmk_r1, &pairwise,
|
|
&vlan, &identity, &identity_len,
|
|
&radius_cui, &radius_cui_len,
|
|
&session_timeout) == 0) {
|
|
wpa_printf(MSG_DEBUG,
|
|
"FT: Generated PMK-R1 based on local PMK-R0");
|
|
goto pmk_r1_derived;
|
|
}
|
|
|
|
if (wpa_ft_pull_pmk_r1(sm, ies, ies_len, parse.rsn_pmkid) < 0) {
|
|
wpa_printf(MSG_DEBUG,
|
|
"FT: Did not have matching PMK-R1 and either unknown or blocked R0KH-ID or NAK from R0KH");
|
|
return WLAN_STATUS_INVALID_PMKID;
|
|
}
|
|
|
|
return -1; /* Status pending */
|
|
} else {
|
|
wpa_printf(MSG_DEBUG, "FT: Found PMKR1Name from local cache");
|
|
}
|
|
|
|
pmk_r1_derived:
|
|
wpa_hexdump_key(MSG_DEBUG, "FT: Selected PMK-R1", pmk_r1, pmk_r1_len);
|
|
sm->pmk_r1_name_valid = 1;
|
|
os_memcpy(sm->pmk_r1_name, pmk_r1_name, WPA_PMK_NAME_LEN);
|
|
os_memcpy(sm->pmk_r1, pmk_r1, pmk_r1_len);
|
|
sm->pmk_r1_len = pmk_r1_len;
|
|
|
|
if (random_get_bytes(sm->ANonce, WPA_NONCE_LEN)) {
|
|
wpa_printf(MSG_DEBUG, "FT: Failed to get random data for "
|
|
"ANonce");
|
|
return WLAN_STATUS_UNSPECIFIED_FAILURE;
|
|
}
|
|
|
|
wpa_hexdump(MSG_DEBUG, "FT: Received SNonce",
|
|
sm->SNonce, WPA_NONCE_LEN);
|
|
wpa_hexdump(MSG_DEBUG, "FT: Generated ANonce",
|
|
sm->ANonce, WPA_NONCE_LEN);
|
|
|
|
if (sm->wpa_auth->conf.force_kdk_derivation ||
|
|
(sm->wpa_auth->conf.secure_ltf &&
|
|
sm->rsnxe && sm->rsnxe_len >= 4 &&
|
|
sm->rsnxe[3] & BIT(WLAN_RSNX_CAPAB_SECURE_LTF - 8)))
|
|
kdk_len = WPA_KDK_MAX_LEN;
|
|
else
|
|
kdk_len = 0;
|
|
|
|
if (wpa_pmk_r1_to_ptk(pmk_r1, pmk_r1_len, sm->SNonce, sm->ANonce,
|
|
sm->addr, sm->wpa_auth->addr, pmk_r1_name,
|
|
&sm->PTK, ptk_name, sm->wpa_key_mgmt,
|
|
pairwise, kdk_len) < 0)
|
|
return WLAN_STATUS_UNSPECIFIED_FAILURE;
|
|
|
|
sm->pairwise = pairwise;
|
|
sm->PTK_valid = true;
|
|
sm->tk_already_set = false;
|
|
wpa_ft_install_ptk(sm, 0);
|
|
|
|
if (wpa_ft_set_vlan(sm->wpa_auth, sm->addr, &vlan) < 0) {
|
|
wpa_printf(MSG_DEBUG, "FT: Failed to configure VLAN");
|
|
return WLAN_STATUS_UNSPECIFIED_FAILURE;
|
|
}
|
|
if (wpa_ft_set_identity(sm->wpa_auth, sm->addr,
|
|
identity, identity_len) < 0 ||
|
|
wpa_ft_set_radius_cui(sm->wpa_auth, sm->addr,
|
|
radius_cui, radius_cui_len) < 0) {
|
|
wpa_printf(MSG_DEBUG, "FT: Failed to configure identity/CUI");
|
|
return WLAN_STATUS_UNSPECIFIED_FAILURE;
|
|
}
|
|
wpa_ft_set_session_timeout(sm->wpa_auth, sm->addr, session_timeout);
|
|
|
|
buflen = 2 + sizeof(struct rsn_mdie) + 2 + sizeof(struct rsn_ftie) +
|
|
2 + FT_R1KH_ID_LEN + 200;
|
|
*resp_ies = os_zalloc(buflen);
|
|
if (*resp_ies == NULL)
|
|
goto fail;
|
|
|
|
pos = *resp_ies;
|
|
end = *resp_ies + buflen;
|
|
|
|
ret = wpa_write_rsn_ie(conf, pos, end - pos, parse.rsn_pmkid);
|
|
if (ret < 0)
|
|
goto fail;
|
|
pos += ret;
|
|
|
|
ret = wpa_write_mdie(conf, pos, end - pos);
|
|
if (ret < 0)
|
|
goto fail;
|
|
pos += ret;
|
|
|
|
ret = wpa_write_ftie(conf, use_sha384, parse.r0kh_id, parse.r0kh_id_len,
|
|
sm->ANonce, sm->SNonce, pos, end - pos, NULL, 0,
|
|
0);
|
|
if (ret < 0)
|
|
goto fail;
|
|
pos += ret;
|
|
|
|
*resp_ies_len = pos - *resp_ies;
|
|
|
|
return WLAN_STATUS_SUCCESS;
|
|
fail:
|
|
os_free(*resp_ies);
|
|
*resp_ies = NULL;
|
|
return WLAN_STATUS_UNSPECIFIED_FAILURE;
|
|
}
|
|
|
|
|
|
void wpa_ft_process_auth(struct wpa_state_machine *sm, const u8 *bssid,
|
|
u16 auth_transaction, const u8 *ies, size_t ies_len,
|
|
void (*cb)(void *ctx, const u8 *dst, const u8 *bssid,
|
|
u16 auth_transaction, u16 status,
|
|
const u8 *ies, size_t ies_len),
|
|
void *ctx)
|
|
{
|
|
u16 status;
|
|
u8 *resp_ies;
|
|
size_t resp_ies_len;
|
|
int res;
|
|
|
|
if (sm == NULL) {
|
|
wpa_printf(MSG_DEBUG, "FT: Received authentication frame, but "
|
|
"WPA SM not available");
|
|
return;
|
|
}
|
|
|
|
wpa_printf(MSG_DEBUG, "FT: Received authentication frame: STA=" MACSTR
|
|
" BSSID=" MACSTR " transaction=%d",
|
|
MAC2STR(sm->addr), MAC2STR(bssid), auth_transaction);
|
|
sm->ft_pending_cb = cb;
|
|
sm->ft_pending_cb_ctx = ctx;
|
|
sm->ft_pending_auth_transaction = auth_transaction;
|
|
sm->ft_pending_pull_left_retries = sm->wpa_auth->conf.rkh_pull_retries;
|
|
res = wpa_ft_process_auth_req(sm, ies, ies_len, &resp_ies,
|
|
&resp_ies_len);
|
|
if (res < 0) {
|
|
wpa_printf(MSG_DEBUG, "FT: Callback postponed until response is available");
|
|
return;
|
|
}
|
|
status = res;
|
|
|
|
wpa_printf(MSG_DEBUG, "FT: FT authentication response: dst=" MACSTR
|
|
" auth_transaction=%d status=%u (%s)",
|
|
MAC2STR(sm->addr), auth_transaction + 1, status,
|
|
status2str(status));
|
|
wpa_hexdump(MSG_DEBUG, "FT: Response IEs", resp_ies, resp_ies_len);
|
|
cb(ctx, sm->addr, bssid, auth_transaction + 1, status,
|
|
resp_ies, resp_ies_len);
|
|
os_free(resp_ies);
|
|
}
|
|
|
|
|
|
int wpa_ft_validate_reassoc(struct wpa_state_machine *sm, const u8 *ies,
|
|
size_t ies_len)
|
|
{
|
|
struct wpa_ft_ies parse;
|
|
struct rsn_mdie *mdie;
|
|
u8 mic[WPA_EAPOL_KEY_MIC_MAX_LEN];
|
|
size_t mic_len = 16;
|
|
unsigned int count;
|
|
const u8 *kck;
|
|
size_t kck_len;
|
|
int use_sha384;
|
|
const u8 *anonce, *snonce, *fte_mic;
|
|
u8 fte_elem_count;
|
|
int rsnxe_used;
|
|
struct wpa_auth_config *conf;
|
|
|
|
if (sm == NULL)
|
|
return WLAN_STATUS_UNSPECIFIED_FAILURE;
|
|
|
|
conf = &sm->wpa_auth->conf;
|
|
use_sha384 = wpa_key_mgmt_sha384(sm->wpa_key_mgmt);
|
|
|
|
wpa_hexdump(MSG_DEBUG, "FT: Reassoc Req IEs", ies, ies_len);
|
|
|
|
if (wpa_ft_parse_ies(ies, ies_len, &parse, use_sha384) < 0) {
|
|
wpa_printf(MSG_DEBUG, "FT: Failed to parse FT IEs");
|
|
return WLAN_STATUS_UNSPECIFIED_FAILURE;
|
|
}
|
|
|
|
if (parse.rsn == NULL) {
|
|
wpa_printf(MSG_DEBUG, "FT: No RSNIE in Reassoc Req");
|
|
return WLAN_STATUS_UNSPECIFIED_FAILURE;
|
|
}
|
|
|
|
if (parse.rsn_pmkid == NULL) {
|
|
wpa_printf(MSG_DEBUG, "FT: No PMKID in RSNIE");
|
|
return WLAN_STATUS_INVALID_PMKID;
|
|
}
|
|
|
|
if (os_memcmp_const(parse.rsn_pmkid, sm->pmk_r1_name, WPA_PMK_NAME_LEN)
|
|
!= 0) {
|
|
wpa_printf(MSG_DEBUG, "FT: PMKID in Reassoc Req did not match "
|
|
"with the PMKR1Name derived from auth request");
|
|
return WLAN_STATUS_INVALID_PMKID;
|
|
}
|
|
|
|
mdie = (struct rsn_mdie *) parse.mdie;
|
|
if (mdie == NULL || parse.mdie_len < sizeof(*mdie) ||
|
|
os_memcmp(mdie->mobility_domain, conf->mobility_domain,
|
|
MOBILITY_DOMAIN_ID_LEN) != 0) {
|
|
wpa_printf(MSG_DEBUG, "FT: Invalid MDIE");
|
|
return WLAN_STATUS_INVALID_MDIE;
|
|
}
|
|
|
|
if (use_sha384) {
|
|
struct rsn_ftie_sha384 *ftie;
|
|
|
|
ftie = (struct rsn_ftie_sha384 *) parse.ftie;
|
|
if (ftie == NULL || parse.ftie_len < sizeof(*ftie)) {
|
|
wpa_printf(MSG_DEBUG, "FT: Invalid FTIE");
|
|
return WLAN_STATUS_INVALID_FTIE;
|
|
}
|
|
|
|
anonce = ftie->anonce;
|
|
snonce = ftie->snonce;
|
|
rsnxe_used = ftie->mic_control[0] & 0x01;
|
|
fte_elem_count = ftie->mic_control[1];
|
|
fte_mic = ftie->mic;
|
|
} else {
|
|
struct rsn_ftie *ftie;
|
|
|
|
ftie = (struct rsn_ftie *) parse.ftie;
|
|
if (ftie == NULL || parse.ftie_len < sizeof(*ftie)) {
|
|
wpa_printf(MSG_DEBUG, "FT: Invalid FTIE");
|
|
return WLAN_STATUS_INVALID_FTIE;
|
|
}
|
|
|
|
anonce = ftie->anonce;
|
|
snonce = ftie->snonce;
|
|
rsnxe_used = ftie->mic_control[0] & 0x01;
|
|
fte_elem_count = ftie->mic_control[1];
|
|
fte_mic = ftie->mic;
|
|
}
|
|
|
|
if (os_memcmp(snonce, sm->SNonce, WPA_NONCE_LEN) != 0) {
|
|
wpa_printf(MSG_DEBUG, "FT: SNonce mismatch in FTIE");
|
|
wpa_hexdump(MSG_DEBUG, "FT: Received SNonce",
|
|
snonce, WPA_NONCE_LEN);
|
|
wpa_hexdump(MSG_DEBUG, "FT: Expected SNonce",
|
|
sm->SNonce, WPA_NONCE_LEN);
|
|
return WLAN_STATUS_INVALID_FTIE;
|
|
}
|
|
|
|
if (os_memcmp(anonce, sm->ANonce, WPA_NONCE_LEN) != 0) {
|
|
wpa_printf(MSG_DEBUG, "FT: ANonce mismatch in FTIE");
|
|
wpa_hexdump(MSG_DEBUG, "FT: Received ANonce",
|
|
anonce, WPA_NONCE_LEN);
|
|
wpa_hexdump(MSG_DEBUG, "FT: Expected ANonce",
|
|
sm->ANonce, WPA_NONCE_LEN);
|
|
return WLAN_STATUS_INVALID_FTIE;
|
|
}
|
|
|
|
|
|
if (parse.r0kh_id == NULL) {
|
|
wpa_printf(MSG_DEBUG, "FT: No R0KH-ID subelem in FTIE");
|
|
return WLAN_STATUS_INVALID_FTIE;
|
|
}
|
|
|
|
if (parse.r0kh_id_len != sm->r0kh_id_len ||
|
|
os_memcmp_const(parse.r0kh_id, sm->r0kh_id, parse.r0kh_id_len) != 0)
|
|
{
|
|
wpa_printf(MSG_DEBUG, "FT: R0KH-ID in FTIE did not match with "
|
|
"the current R0KH-ID");
|
|
wpa_hexdump(MSG_DEBUG, "FT: R0KH-ID in FTIE",
|
|
parse.r0kh_id, parse.r0kh_id_len);
|
|
wpa_hexdump(MSG_DEBUG, "FT: The current R0KH-ID",
|
|
sm->r0kh_id, sm->r0kh_id_len);
|
|
return WLAN_STATUS_INVALID_FTIE;
|
|
}
|
|
|
|
if (parse.r1kh_id == NULL) {
|
|
wpa_printf(MSG_DEBUG, "FT: No R1KH-ID subelem in FTIE");
|
|
return WLAN_STATUS_INVALID_FTIE;
|
|
}
|
|
|
|
if (os_memcmp_const(parse.r1kh_id, conf->r1_key_holder,
|
|
FT_R1KH_ID_LEN) != 0) {
|
|
wpa_printf(MSG_DEBUG, "FT: Unknown R1KH-ID used in "
|
|
"ReassocReq");
|
|
wpa_hexdump(MSG_DEBUG, "FT: R1KH-ID in FTIE",
|
|
parse.r1kh_id, FT_R1KH_ID_LEN);
|
|
wpa_hexdump(MSG_DEBUG, "FT: Expected R1KH-ID",
|
|
conf->r1_key_holder, FT_R1KH_ID_LEN);
|
|
return WLAN_STATUS_INVALID_FTIE;
|
|
}
|
|
|
|
if (parse.rsn_pmkid == NULL ||
|
|
os_memcmp_const(parse.rsn_pmkid, sm->pmk_r1_name, WPA_PMK_NAME_LEN))
|
|
{
|
|
wpa_printf(MSG_DEBUG, "FT: No matching PMKR1Name (PMKID) in "
|
|
"RSNIE (pmkid=%d)", !!parse.rsn_pmkid);
|
|
return WLAN_STATUS_INVALID_PMKID;
|
|
}
|
|
|
|
count = 3;
|
|
if (parse.ric)
|
|
count += ieee802_11_ie_count(parse.ric, parse.ric_len);
|
|
if (parse.rsnxe)
|
|
count++;
|
|
if (fte_elem_count != count) {
|
|
wpa_printf(MSG_DEBUG, "FT: Unexpected IE count in MIC "
|
|
"Control: received %u expected %u",
|
|
fte_elem_count, count);
|
|
return WLAN_STATUS_UNSPECIFIED_FAILURE;
|
|
}
|
|
|
|
if (wpa_key_mgmt_fils(sm->wpa_key_mgmt)) {
|
|
kck = sm->PTK.kck2;
|
|
kck_len = sm->PTK.kck2_len;
|
|
} else {
|
|
kck = sm->PTK.kck;
|
|
kck_len = sm->PTK.kck_len;
|
|
}
|
|
if (wpa_ft_mic(kck, kck_len, sm->addr, sm->wpa_auth->addr, 5,
|
|
parse.mdie - 2, parse.mdie_len + 2,
|
|
parse.ftie - 2, parse.ftie_len + 2,
|
|
parse.rsn - 2, parse.rsn_len + 2,
|
|
parse.ric, parse.ric_len,
|
|
parse.rsnxe ? parse.rsnxe - 2 : NULL,
|
|
parse.rsnxe ? parse.rsnxe_len + 2 : 0,
|
|
mic) < 0) {
|
|
wpa_printf(MSG_DEBUG, "FT: Failed to calculate MIC");
|
|
return WLAN_STATUS_UNSPECIFIED_FAILURE;
|
|
}
|
|
|
|
if (os_memcmp_const(mic, fte_mic, mic_len) != 0) {
|
|
wpa_printf(MSG_DEBUG, "FT: Invalid MIC in FTIE");
|
|
wpa_printf(MSG_DEBUG, "FT: addr=" MACSTR " auth_addr=" MACSTR,
|
|
MAC2STR(sm->addr), MAC2STR(sm->wpa_auth->addr));
|
|
wpa_hexdump(MSG_MSGDUMP, "FT: Received MIC",
|
|
fte_mic, mic_len);
|
|
wpa_hexdump(MSG_MSGDUMP, "FT: Calculated MIC", mic, mic_len);
|
|
wpa_hexdump(MSG_MSGDUMP, "FT: MDIE",
|
|
parse.mdie - 2, parse.mdie_len + 2);
|
|
wpa_hexdump(MSG_MSGDUMP, "FT: FTIE",
|
|
parse.ftie - 2, parse.ftie_len + 2);
|
|
wpa_hexdump(MSG_MSGDUMP, "FT: RSN",
|
|
parse.rsn - 2, parse.rsn_len + 2);
|
|
wpa_hexdump(MSG_MSGDUMP, "FT: RSNXE",
|
|
parse.rsnxe ? parse.rsnxe - 2 : NULL,
|
|
parse.rsnxe ? parse.rsnxe_len + 2 : 0);
|
|
return WLAN_STATUS_INVALID_FTIE;
|
|
}
|
|
|
|
if (rsnxe_used && (conf->sae_pwe == 1 || conf->sae_pwe == 2) &&
|
|
!parse.rsnxe) {
|
|
wpa_printf(MSG_INFO,
|
|
"FT: FTE indicated that STA uses RSNXE, but RSNXE was not included");
|
|
return -1; /* discard request */
|
|
}
|
|
|
|
#ifdef CONFIG_OCV
|
|
if (wpa_auth_uses_ocv(sm)) {
|
|
struct wpa_channel_info ci;
|
|
int tx_chanwidth;
|
|
int tx_seg1_idx;
|
|
enum oci_verify_result res;
|
|
|
|
if (wpa_channel_info(sm->wpa_auth, &ci) != 0) {
|
|
wpa_printf(MSG_WARNING,
|
|
"Failed to get channel info to validate received OCI in (Re)Assoc Request");
|
|
return WLAN_STATUS_UNSPECIFIED_FAILURE;
|
|
}
|
|
|
|
if (get_sta_tx_parameters(sm,
|
|
channel_width_to_int(ci.chanwidth),
|
|
ci.seg1_idx, &tx_chanwidth,
|
|
&tx_seg1_idx) < 0)
|
|
return WLAN_STATUS_UNSPECIFIED_FAILURE;
|
|
|
|
res = ocv_verify_tx_params(parse.oci, parse.oci_len, &ci,
|
|
tx_chanwidth, tx_seg1_idx);
|
|
if (wpa_auth_uses_ocv(sm) == 2 && res == OCI_NOT_FOUND) {
|
|
/* Work around misbehaving STAs */
|
|
wpa_printf(MSG_INFO,
|
|
"Disable OCV with a STA that does not send OCI");
|
|
wpa_auth_set_ocv(sm, 0);
|
|
} else if (res != OCI_SUCCESS) {
|
|
wpa_printf(MSG_WARNING, "OCV failed: %s", ocv_errorstr);
|
|
if (sm->wpa_auth->conf.msg_ctx)
|
|
wpa_msg(sm->wpa_auth->conf.msg_ctx, MSG_INFO,
|
|
OCV_FAILURE "addr=" MACSTR
|
|
" frame=ft-reassoc-req error=%s",
|
|
MAC2STR(sm->addr), ocv_errorstr);
|
|
return WLAN_STATUS_INVALID_FTIE;
|
|
}
|
|
}
|
|
#endif /* CONFIG_OCV */
|
|
|
|
return WLAN_STATUS_SUCCESS;
|
|
}
|
|
|
|
|
|
int wpa_ft_action_rx(struct wpa_state_machine *sm, const u8 *data, size_t len)
|
|
{
|
|
const u8 *sta_addr, *target_ap;
|
|
const u8 *ies;
|
|
size_t ies_len;
|
|
u8 action;
|
|
struct ft_rrb_frame *frame;
|
|
|
|
if (sm == NULL)
|
|
return -1;
|
|
|
|
/*
|
|
* data: Category[1] Action[1] STA_Address[6] Target_AP_Address[6]
|
|
* FT Request action frame body[variable]
|
|
*/
|
|
|
|
if (len < 14) {
|
|
wpa_printf(MSG_DEBUG, "FT: Too short FT Action frame "
|
|
"(len=%lu)", (unsigned long) len);
|
|
return -1;
|
|
}
|
|
|
|
action = data[1];
|
|
sta_addr = data + 2;
|
|
target_ap = data + 8;
|
|
ies = data + 14;
|
|
ies_len = len - 14;
|
|
|
|
wpa_printf(MSG_DEBUG, "FT: Received FT Action frame (STA=" MACSTR
|
|
" Target AP=" MACSTR " Action=%d)",
|
|
MAC2STR(sta_addr), MAC2STR(target_ap), action);
|
|
|
|
if (os_memcmp(sta_addr, sm->addr, ETH_ALEN) != 0) {
|
|
wpa_printf(MSG_DEBUG, "FT: Mismatch in FT Action STA address: "
|
|
"STA=" MACSTR " STA-Address=" MACSTR,
|
|
MAC2STR(sm->addr), MAC2STR(sta_addr));
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* Do some sanity checking on the target AP address (not own and not
|
|
* broadcast. This could be extended to filter based on a list of known
|
|
* APs in the MD (if such a list were configured).
|
|
*/
|
|
if ((target_ap[0] & 0x01) ||
|
|
os_memcmp(target_ap, sm->wpa_auth->addr, ETH_ALEN) == 0) {
|
|
wpa_printf(MSG_DEBUG, "FT: Invalid Target AP in FT Action "
|
|
"frame");
|
|
return -1;
|
|
}
|
|
|
|
wpa_hexdump(MSG_MSGDUMP, "FT: Action frame body", ies, ies_len);
|
|
|
|
if (!sm->wpa_auth->conf.ft_over_ds) {
|
|
wpa_printf(MSG_DEBUG, "FT: Over-DS option disabled - reject");
|
|
return -1;
|
|
}
|
|
|
|
/* RRB - Forward action frame to the target AP */
|
|
frame = os_malloc(sizeof(*frame) + len);
|
|
if (frame == NULL)
|
|
return -1;
|
|
frame->frame_type = RSN_REMOTE_FRAME_TYPE_FT_RRB;
|
|
frame->packet_type = FT_PACKET_REQUEST;
|
|
frame->action_length = host_to_le16(len);
|
|
os_memcpy(frame->ap_address, sm->wpa_auth->addr, ETH_ALEN);
|
|
os_memcpy(frame + 1, data, len);
|
|
|
|
wpa_ft_rrb_send(sm->wpa_auth, target_ap, (u8 *) frame,
|
|
sizeof(*frame) + len);
|
|
os_free(frame);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static void wpa_ft_rrb_rx_request_cb(void *ctx, const u8 *dst, const u8 *bssid,
|
|
u16 auth_transaction, u16 resp,
|
|
const u8 *ies, size_t ies_len)
|
|
{
|
|
struct wpa_state_machine *sm = ctx;
|
|
wpa_printf(MSG_DEBUG, "FT: Over-the-DS RX request cb for " MACSTR,
|
|
MAC2STR(sm->addr));
|
|
wpa_ft_send_rrb_auth_resp(sm, sm->ft_pending_current_ap, sm->addr,
|
|
WLAN_STATUS_SUCCESS, ies, ies_len);
|
|
}
|
|
|
|
|
|
static int wpa_ft_rrb_rx_request(struct wpa_authenticator *wpa_auth,
|
|
const u8 *current_ap, const u8 *sta_addr,
|
|
const u8 *body, size_t len)
|
|
{
|
|
struct wpa_state_machine *sm;
|
|
u16 status;
|
|
u8 *resp_ies;
|
|
size_t resp_ies_len;
|
|
int res;
|
|
|
|
sm = wpa_ft_add_sta(wpa_auth, sta_addr);
|
|
if (sm == NULL) {
|
|
wpa_printf(MSG_DEBUG, "FT: Failed to add new STA based on "
|
|
"RRB Request");
|
|
return -1;
|
|
}
|
|
|
|
wpa_hexdump(MSG_MSGDUMP, "FT: RRB Request Frame body", body, len);
|
|
|
|
sm->ft_pending_cb = wpa_ft_rrb_rx_request_cb;
|
|
sm->ft_pending_cb_ctx = sm;
|
|
os_memcpy(sm->ft_pending_current_ap, current_ap, ETH_ALEN);
|
|
sm->ft_pending_pull_left_retries = sm->wpa_auth->conf.rkh_pull_retries;
|
|
res = wpa_ft_process_auth_req(sm, body, len, &resp_ies,
|
|
&resp_ies_len);
|
|
if (res < 0) {
|
|
wpa_printf(MSG_DEBUG, "FT: No immediate response available - wait for pull response");
|
|
return 0;
|
|
}
|
|
status = res;
|
|
|
|
res = wpa_ft_send_rrb_auth_resp(sm, current_ap, sta_addr, status,
|
|
resp_ies, resp_ies_len);
|
|
os_free(resp_ies);
|
|
return res;
|
|
}
|
|
|
|
|
|
static int wpa_ft_send_rrb_auth_resp(struct wpa_state_machine *sm,
|
|
const u8 *current_ap, const u8 *sta_addr,
|
|
u16 status, const u8 *resp_ies,
|
|
size_t resp_ies_len)
|
|
{
|
|
struct wpa_authenticator *wpa_auth = sm->wpa_auth;
|
|
size_t rlen;
|
|
struct ft_rrb_frame *frame;
|
|
u8 *pos;
|
|
|
|
wpa_printf(MSG_DEBUG, "FT: RRB authentication response: STA=" MACSTR
|
|
" CurrentAP=" MACSTR " status=%u (%s)",
|
|
MAC2STR(sm->addr), MAC2STR(current_ap), status,
|
|
status2str(status));
|
|
wpa_hexdump(MSG_DEBUG, "FT: Response IEs", resp_ies, resp_ies_len);
|
|
|
|
/* RRB - Forward action frame response to the Current AP */
|
|
|
|
/*
|
|
* data: Category[1] Action[1] STA_Address[6] Target_AP_Address[6]
|
|
* Status_Code[2] FT Request action frame body[variable]
|
|
*/
|
|
rlen = 2 + 2 * ETH_ALEN + 2 + resp_ies_len;
|
|
|
|
frame = os_malloc(sizeof(*frame) + rlen);
|
|
if (frame == NULL)
|
|
return -1;
|
|
frame->frame_type = RSN_REMOTE_FRAME_TYPE_FT_RRB;
|
|
frame->packet_type = FT_PACKET_RESPONSE;
|
|
frame->action_length = host_to_le16(rlen);
|
|
os_memcpy(frame->ap_address, wpa_auth->addr, ETH_ALEN);
|
|
pos = (u8 *) (frame + 1);
|
|
*pos++ = WLAN_ACTION_FT;
|
|
*pos++ = 2; /* Action: Response */
|
|
os_memcpy(pos, sta_addr, ETH_ALEN);
|
|
pos += ETH_ALEN;
|
|
os_memcpy(pos, wpa_auth->addr, ETH_ALEN);
|
|
pos += ETH_ALEN;
|
|
WPA_PUT_LE16(pos, status);
|
|
pos += 2;
|
|
if (resp_ies)
|
|
os_memcpy(pos, resp_ies, resp_ies_len);
|
|
|
|
wpa_ft_rrb_send(wpa_auth, current_ap, (u8 *) frame,
|
|
sizeof(*frame) + rlen);
|
|
os_free(frame);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int wpa_ft_rrb_build_r0(const u8 *key, const size_t key_len,
|
|
const struct tlv_list *tlvs,
|
|
const struct wpa_ft_pmk_r0_sa *pmk_r0,
|
|
const u8 *r1kh_id, const u8 *s1kh_id,
|
|
const struct tlv_list *tlv_auth,
|
|
const u8 *src_addr, u8 type,
|
|
u8 **packet, size_t *packet_len)
|
|
{
|
|
u8 pmk_r1[PMK_LEN_MAX];
|
|
size_t pmk_r1_len = pmk_r0->pmk_r0_len;
|
|
u8 pmk_r1_name[WPA_PMK_NAME_LEN];
|
|
u8 f_pairwise[sizeof(le16)];
|
|
u8 f_expires_in[sizeof(le16)];
|
|
u8 f_session_timeout[sizeof(le32)];
|
|
int expires_in;
|
|
int session_timeout;
|
|
struct os_reltime now;
|
|
int ret;
|
|
struct tlv_list sess_tlv[] = {
|
|
{ .type = FT_RRB_PMK_R1, .len = pmk_r1_len,
|
|
.data = pmk_r1 },
|
|
{ .type = FT_RRB_PMK_R1_NAME, .len = sizeof(pmk_r1_name),
|
|
.data = pmk_r1_name },
|
|
{ .type = FT_RRB_PAIRWISE, .len = sizeof(f_pairwise),
|
|
.data = f_pairwise },
|
|
{ .type = FT_RRB_EXPIRES_IN, .len = sizeof(f_expires_in),
|
|
.data = f_expires_in },
|
|
{ .type = FT_RRB_IDENTITY, .len = pmk_r0->identity_len,
|
|
.data = pmk_r0->identity },
|
|
{ .type = FT_RRB_RADIUS_CUI, .len = pmk_r0->radius_cui_len,
|
|
.data = pmk_r0->radius_cui },
|
|
{ .type = FT_RRB_SESSION_TIMEOUT,
|
|
.len = sizeof(f_session_timeout),
|
|
.data = f_session_timeout },
|
|
{ .type = FT_RRB_LAST_EMPTY, .len = 0, .data = NULL },
|
|
};
|
|
|
|
wpa_printf(MSG_DEBUG, "FT: Derive PMK-R1 for peer AP");
|
|
if (wpa_derive_pmk_r1(pmk_r0->pmk_r0, pmk_r0->pmk_r0_len,
|
|
pmk_r0->pmk_r0_name, r1kh_id,
|
|
s1kh_id, pmk_r1, pmk_r1_name) < 0)
|
|
return -1;
|
|
WPA_PUT_LE16(f_pairwise, pmk_r0->pairwise);
|
|
|
|
os_get_reltime(&now);
|
|
if (pmk_r0->expiration > now.sec)
|
|
expires_in = pmk_r0->expiration - now.sec;
|
|
else if (pmk_r0->expiration)
|
|
expires_in = 1;
|
|
else
|
|
expires_in = 0;
|
|
WPA_PUT_LE16(f_expires_in, expires_in);
|
|
|
|
if (pmk_r0->session_timeout > now.sec)
|
|
session_timeout = pmk_r0->session_timeout - now.sec;
|
|
else if (pmk_r0->session_timeout)
|
|
session_timeout = 1;
|
|
else
|
|
session_timeout = 0;
|
|
WPA_PUT_LE32(f_session_timeout, session_timeout);
|
|
|
|
ret = wpa_ft_rrb_build(key, key_len, tlvs, sess_tlv, tlv_auth,
|
|
pmk_r0->vlan, src_addr, type,
|
|
packet, packet_len);
|
|
|
|
forced_memzero(pmk_r1, sizeof(pmk_r1));
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int wpa_ft_rrb_rx_pull(struct wpa_authenticator *wpa_auth,
|
|
const u8 *src_addr,
|
|
const u8 *enc, size_t enc_len,
|
|
const u8 *auth, size_t auth_len,
|
|
int no_defer)
|
|
{
|
|
const char *msgtype = "pull request";
|
|
u8 *plain = NULL, *packet = NULL;
|
|
size_t plain_len = 0, packet_len = 0;
|
|
struct ft_remote_r1kh *r1kh, *r1kh_wildcard;
|
|
const u8 *key;
|
|
size_t key_len;
|
|
int seq_ret;
|
|
const u8 *f_nonce, *f_r0kh_id, *f_r1kh_id, *f_s1kh_id, *f_pmk_r0_name;
|
|
size_t f_nonce_len, f_r0kh_id_len, f_r1kh_id_len, f_s1kh_id_len;
|
|
size_t f_pmk_r0_name_len;
|
|
const struct wpa_ft_pmk_r0_sa *r0;
|
|
int ret;
|
|
struct tlv_list resp[2];
|
|
struct tlv_list resp_auth[5];
|
|
struct ft_rrb_seq f_seq;
|
|
|
|
wpa_printf(MSG_DEBUG, "FT: Received PMK-R1 pull");
|
|
|
|
RRB_GET_AUTH(FT_RRB_R0KH_ID, r0kh_id, msgtype, -1);
|
|
wpa_hexdump(MSG_DEBUG, "FT: R0KH-ID", f_r0kh_id, f_r0kh_id_len);
|
|
|
|
if (wpa_ft_rrb_check_r0kh(wpa_auth, f_r0kh_id, f_r0kh_id_len)) {
|
|
wpa_printf(MSG_DEBUG, "FT: R0KH-ID mismatch");
|
|
goto out;
|
|
}
|
|
|
|
RRB_GET_AUTH(FT_RRB_R1KH_ID, r1kh_id, msgtype, FT_R1KH_ID_LEN);
|
|
wpa_printf(MSG_DEBUG, "FT: R1KH-ID=" MACSTR, MAC2STR(f_r1kh_id));
|
|
|
|
wpa_ft_rrb_lookup_r1kh(wpa_auth, f_r1kh_id, &r1kh, &r1kh_wildcard);
|
|
if (r1kh) {
|
|
key = r1kh->key;
|
|
key_len = sizeof(r1kh->key);
|
|
} else if (r1kh_wildcard) {
|
|
wpa_printf(MSG_DEBUG, "FT: Using wildcard R1KH-ID");
|
|
key = r1kh_wildcard->key;
|
|
key_len = sizeof(r1kh_wildcard->key);
|
|
} else {
|
|
goto out;
|
|
}
|
|
|
|
RRB_GET_AUTH(FT_RRB_NONCE, nonce, "pull request", FT_RRB_NONCE_LEN);
|
|
wpa_hexdump(MSG_DEBUG, "FT: nonce", f_nonce, f_nonce_len);
|
|
|
|
seq_ret = FT_RRB_SEQ_DROP;
|
|
if (r1kh)
|
|
seq_ret = wpa_ft_rrb_seq_chk(r1kh->seq, src_addr, enc, enc_len,
|
|
auth, auth_len, msgtype, no_defer);
|
|
if (!no_defer && r1kh_wildcard &&
|
|
(!r1kh || os_memcmp(r1kh->addr, src_addr, ETH_ALEN) != 0)) {
|
|
/* wildcard: r1kh-id unknown or changed addr -> do a seq req */
|
|
seq_ret = FT_RRB_SEQ_DEFER;
|
|
}
|
|
|
|
if (seq_ret == FT_RRB_SEQ_DROP)
|
|
goto out;
|
|
|
|
if (wpa_ft_rrb_decrypt(key, key_len, enc, enc_len, auth, auth_len,
|
|
src_addr, FT_PACKET_R0KH_R1KH_PULL,
|
|
&plain, &plain_len) < 0)
|
|
goto out;
|
|
|
|
if (!r1kh)
|
|
r1kh = wpa_ft_rrb_add_r1kh(wpa_auth, r1kh_wildcard, src_addr,
|
|
f_r1kh_id,
|
|
wpa_auth->conf.rkh_pos_timeout);
|
|
if (!r1kh)
|
|
goto out;
|
|
|
|
if (seq_ret == FT_RRB_SEQ_DEFER) {
|
|
wpa_ft_rrb_seq_req(wpa_auth, r1kh->seq, src_addr, f_r0kh_id,
|
|
f_r0kh_id_len, f_r1kh_id, key, key_len,
|
|
enc, enc_len, auth, auth_len,
|
|
&wpa_ft_rrb_rx_pull);
|
|
goto out;
|
|
}
|
|
|
|
wpa_ft_rrb_seq_accept(wpa_auth, r1kh->seq, src_addr, auth, auth_len,
|
|
msgtype);
|
|
wpa_ft_rrb_r1kh_replenish(wpa_auth, r1kh,
|
|
wpa_auth->conf.rkh_pos_timeout);
|
|
|
|
RRB_GET(FT_RRB_PMK_R0_NAME, pmk_r0_name, msgtype, WPA_PMK_NAME_LEN);
|
|
wpa_hexdump(MSG_DEBUG, "FT: PMKR0Name", f_pmk_r0_name,
|
|
f_pmk_r0_name_len);
|
|
|
|
RRB_GET(FT_RRB_S1KH_ID, s1kh_id, msgtype, ETH_ALEN);
|
|
wpa_printf(MSG_DEBUG, "FT: S1KH-ID=" MACSTR, MAC2STR(f_s1kh_id));
|
|
|
|
if (wpa_ft_new_seq(r1kh->seq, &f_seq) < 0) {
|
|
wpa_printf(MSG_DEBUG, "FT: Failed to get seq num");
|
|
goto out;
|
|
}
|
|
|
|
wpa_printf(MSG_DEBUG, "FT: Send PMK-R1 pull response from " MACSTR
|
|
" to " MACSTR,
|
|
MAC2STR(wpa_auth->addr), MAC2STR(src_addr));
|
|
|
|
resp[0].type = FT_RRB_S1KH_ID;
|
|
resp[0].len = f_s1kh_id_len;
|
|
resp[0].data = f_s1kh_id;
|
|
resp[1].type = FT_RRB_LAST_EMPTY;
|
|
resp[1].len = 0;
|
|
resp[1].data = NULL;
|
|
|
|
resp_auth[0].type = FT_RRB_NONCE;
|
|
resp_auth[0].len = f_nonce_len;
|
|
resp_auth[0].data = f_nonce;
|
|
resp_auth[1].type = FT_RRB_SEQ;
|
|
resp_auth[1].len = sizeof(f_seq);
|
|
resp_auth[1].data = (u8 *) &f_seq;
|
|
resp_auth[2].type = FT_RRB_R0KH_ID;
|
|
resp_auth[2].len = f_r0kh_id_len;
|
|
resp_auth[2].data = f_r0kh_id;
|
|
resp_auth[3].type = FT_RRB_R1KH_ID;
|
|
resp_auth[3].len = f_r1kh_id_len;
|
|
resp_auth[3].data = f_r1kh_id;
|
|
resp_auth[4].type = FT_RRB_LAST_EMPTY;
|
|
resp_auth[4].len = 0;
|
|
resp_auth[4].data = NULL;
|
|
|
|
if (wpa_ft_fetch_pmk_r0(wpa_auth, f_s1kh_id, f_pmk_r0_name, &r0) < 0) {
|
|
wpa_printf(MSG_DEBUG, "FT: No matching PMK-R0-Name found");
|
|
ret = wpa_ft_rrb_build(key, key_len, resp, NULL, resp_auth,
|
|
NULL, wpa_auth->addr,
|
|
FT_PACKET_R0KH_R1KH_RESP,
|
|
&packet, &packet_len);
|
|
} else {
|
|
ret = wpa_ft_rrb_build_r0(key, key_len, resp, r0, f_r1kh_id,
|
|
f_s1kh_id, resp_auth, wpa_auth->addr,
|
|
FT_PACKET_R0KH_R1KH_RESP,
|
|
&packet, &packet_len);
|
|
}
|
|
|
|
if (!ret)
|
|
wpa_ft_rrb_oui_send(wpa_auth, src_addr,
|
|
FT_PACKET_R0KH_R1KH_RESP, packet,
|
|
packet_len);
|
|
|
|
out:
|
|
os_free(plain);
|
|
os_free(packet);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* @returns 0 on success
|
|
* -1 on error
|
|
* -2 if FR_RRB_PAIRWISE is missing
|
|
*/
|
|
static int wpa_ft_rrb_rx_r1(struct wpa_authenticator *wpa_auth,
|
|
const u8 *src_addr, u8 type,
|
|
const u8 *enc, size_t enc_len,
|
|
const u8 *auth, size_t auth_len,
|
|
const char *msgtype, u8 *s1kh_id_out,
|
|
int (*cb)(struct wpa_authenticator *wpa_auth,
|
|
const u8 *src_addr,
|
|
const u8 *enc, size_t enc_len,
|
|
const u8 *auth, size_t auth_len,
|
|
int no_defer))
|
|
{
|
|
u8 *plain = NULL;
|
|
size_t plain_len = 0;
|
|
struct ft_remote_r0kh *r0kh, *r0kh_wildcard;
|
|
const u8 *key;
|
|
size_t key_len;
|
|
int seq_ret;
|
|
const u8 *f_r1kh_id, *f_s1kh_id, *f_r0kh_id;
|
|
const u8 *f_pmk_r1_name, *f_pairwise, *f_pmk_r1;
|
|
const u8 *f_expires_in;
|
|
size_t f_r1kh_id_len, f_s1kh_id_len, f_r0kh_id_len;
|
|
const u8 *f_identity, *f_radius_cui;
|
|
const u8 *f_session_timeout;
|
|
size_t f_pmk_r1_name_len, f_pairwise_len, f_pmk_r1_len;
|
|
size_t f_expires_in_len;
|
|
size_t f_identity_len, f_radius_cui_len;
|
|
size_t f_session_timeout_len;
|
|
int pairwise;
|
|
int ret = -1;
|
|
int expires_in;
|
|
int session_timeout;
|
|
struct vlan_description vlan;
|
|
size_t pmk_r1_len;
|
|
|
|
RRB_GET_AUTH(FT_RRB_R0KH_ID, r0kh_id, msgtype, -1);
|
|
wpa_hexdump(MSG_DEBUG, "FT: R0KH-ID", f_r0kh_id, f_r0kh_id_len);
|
|
|
|
RRB_GET_AUTH(FT_RRB_R1KH_ID, r1kh_id, msgtype, FT_R1KH_ID_LEN);
|
|
wpa_printf(MSG_DEBUG, "FT: R1KH-ID=" MACSTR, MAC2STR(f_r1kh_id));
|
|
|
|
if (wpa_ft_rrb_check_r1kh(wpa_auth, f_r1kh_id)) {
|
|
wpa_printf(MSG_DEBUG, "FT: R1KH-ID mismatch");
|
|
goto out;
|
|
}
|
|
|
|
wpa_ft_rrb_lookup_r0kh(wpa_auth, f_r0kh_id, f_r0kh_id_len, &r0kh,
|
|
&r0kh_wildcard);
|
|
if (r0kh) {
|
|
key = r0kh->key;
|
|
key_len = sizeof(r0kh->key);
|
|
} else if (r0kh_wildcard) {
|
|
wpa_printf(MSG_DEBUG, "FT: Using wildcard R0KH-ID");
|
|
key = r0kh_wildcard->key;
|
|
key_len = sizeof(r0kh_wildcard->key);
|
|
} else {
|
|
goto out;
|
|
}
|
|
|
|
seq_ret = FT_RRB_SEQ_DROP;
|
|
if (r0kh) {
|
|
seq_ret = wpa_ft_rrb_seq_chk(r0kh->seq, src_addr, enc, enc_len,
|
|
auth, auth_len, msgtype,
|
|
cb ? 0 : 1);
|
|
}
|
|
if (cb && r0kh_wildcard &&
|
|
(!r0kh || os_memcmp(r0kh->addr, src_addr, ETH_ALEN) != 0)) {
|
|
/* wildcard: r0kh-id unknown or changed addr -> do a seq req */
|
|
seq_ret = FT_RRB_SEQ_DEFER;
|
|
}
|
|
|
|
if (seq_ret == FT_RRB_SEQ_DROP)
|
|
goto out;
|
|
|
|
if (wpa_ft_rrb_decrypt(key, key_len, enc, enc_len, auth, auth_len,
|
|
src_addr, type, &plain, &plain_len) < 0)
|
|
goto out;
|
|
|
|
if (!r0kh)
|
|
r0kh = wpa_ft_rrb_add_r0kh(wpa_auth, r0kh_wildcard, src_addr,
|
|
f_r0kh_id, f_r0kh_id_len,
|
|
wpa_auth->conf.rkh_pos_timeout);
|
|
if (!r0kh)
|
|
goto out;
|
|
|
|
if (seq_ret == FT_RRB_SEQ_DEFER) {
|
|
wpa_ft_rrb_seq_req(wpa_auth, r0kh->seq, src_addr, f_r0kh_id,
|
|
f_r0kh_id_len, f_r1kh_id, key, key_len,
|
|
enc, enc_len, auth, auth_len, cb);
|
|
goto out;
|
|
}
|
|
|
|
wpa_ft_rrb_seq_accept(wpa_auth, r0kh->seq, src_addr, auth, auth_len,
|
|
msgtype);
|
|
wpa_ft_rrb_r0kh_replenish(wpa_auth, r0kh,
|
|
wpa_auth->conf.rkh_pos_timeout);
|
|
|
|
RRB_GET(FT_RRB_S1KH_ID, s1kh_id, msgtype, ETH_ALEN);
|
|
wpa_printf(MSG_DEBUG, "FT: S1KH-ID=" MACSTR, MAC2STR(f_s1kh_id));
|
|
|
|
if (s1kh_id_out)
|
|
os_memcpy(s1kh_id_out, f_s1kh_id, ETH_ALEN);
|
|
|
|
ret = -2;
|
|
RRB_GET(FT_RRB_PAIRWISE, pairwise, msgtype, sizeof(le16));
|
|
wpa_hexdump(MSG_DEBUG, "FT: pairwise", f_pairwise, f_pairwise_len);
|
|
|
|
ret = -1;
|
|
RRB_GET(FT_RRB_PMK_R1_NAME, pmk_r1_name, msgtype, WPA_PMK_NAME_LEN);
|
|
wpa_hexdump(MSG_DEBUG, "FT: PMKR1Name",
|
|
f_pmk_r1_name, WPA_PMK_NAME_LEN);
|
|
|
|
pmk_r1_len = PMK_LEN;
|
|
if (wpa_ft_rrb_get_tlv(plain, plain_len, FT_RRB_PMK_R1, &f_pmk_r1_len,
|
|
&f_pmk_r1) == 0 &&
|
|
(f_pmk_r1_len == PMK_LEN || f_pmk_r1_len == SHA384_MAC_LEN))
|
|
pmk_r1_len = f_pmk_r1_len;
|
|
RRB_GET(FT_RRB_PMK_R1, pmk_r1, msgtype, pmk_r1_len);
|
|
wpa_hexdump_key(MSG_DEBUG, "FT: PMK-R1", f_pmk_r1, pmk_r1_len);
|
|
|
|
pairwise = WPA_GET_LE16(f_pairwise);
|
|
|
|
RRB_GET_OPTIONAL(FT_RRB_EXPIRES_IN, expires_in, msgtype,
|
|
sizeof(le16));
|
|
if (f_expires_in)
|
|
expires_in = WPA_GET_LE16(f_expires_in);
|
|
else
|
|
expires_in = 0;
|
|
|
|
wpa_printf(MSG_DEBUG, "FT: PMK-R1 %s - expires_in=%d", msgtype,
|
|
expires_in);
|
|
|
|
if (wpa_ft_rrb_get_tlv_vlan(plain, plain_len, &vlan) < 0) {
|
|
wpa_printf(MSG_DEBUG, "FT: Cannot parse vlan");
|
|
wpa_ft_rrb_dump(plain, plain_len);
|
|
goto out;
|
|
}
|
|
|
|
wpa_printf(MSG_DEBUG, "FT: vlan %d%s",
|
|
le_to_host16(vlan.untagged), vlan.tagged[0] ? "+" : "");
|
|
|
|
RRB_GET_OPTIONAL(FT_RRB_IDENTITY, identity, msgtype, -1);
|
|
if (f_identity)
|
|
wpa_hexdump_ascii(MSG_DEBUG, "FT: Identity", f_identity,
|
|
f_identity_len);
|
|
|
|
RRB_GET_OPTIONAL(FT_RRB_RADIUS_CUI, radius_cui, msgtype, -1);
|
|
if (f_radius_cui)
|
|
wpa_hexdump_ascii(MSG_DEBUG, "FT: CUI", f_radius_cui,
|
|
f_radius_cui_len);
|
|
|
|
RRB_GET_OPTIONAL(FT_RRB_SESSION_TIMEOUT, session_timeout, msgtype,
|
|
sizeof(le32));
|
|
if (f_session_timeout)
|
|
session_timeout = WPA_GET_LE32(f_session_timeout);
|
|
else
|
|
session_timeout = 0;
|
|
wpa_printf(MSG_DEBUG, "FT: session_timeout %d", session_timeout);
|
|
|
|
if (wpa_ft_store_pmk_r1(wpa_auth, f_s1kh_id, f_pmk_r1, pmk_r1_len,
|
|
f_pmk_r1_name,
|
|
pairwise, &vlan, expires_in, session_timeout,
|
|
f_identity, f_identity_len, f_radius_cui,
|
|
f_radius_cui_len) < 0)
|
|
goto out;
|
|
|
|
ret = 0;
|
|
out:
|
|
bin_clear_free(plain, plain_len);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
static void ft_finish_pull(struct wpa_state_machine *sm)
|
|
{
|
|
int res;
|
|
u8 *resp_ies;
|
|
size_t resp_ies_len;
|
|
u16 status;
|
|
|
|
if (!sm->ft_pending_cb || !sm->ft_pending_req_ies)
|
|
return;
|
|
|
|
res = wpa_ft_process_auth_req(sm, wpabuf_head(sm->ft_pending_req_ies),
|
|
wpabuf_len(sm->ft_pending_req_ies),
|
|
&resp_ies, &resp_ies_len);
|
|
if (res < 0) {
|
|
/* this loop is broken by ft_pending_pull_left_retries */
|
|
wpa_printf(MSG_DEBUG,
|
|
"FT: Callback postponed until response is available");
|
|
return;
|
|
}
|
|
wpabuf_free(sm->ft_pending_req_ies);
|
|
sm->ft_pending_req_ies = NULL;
|
|
status = res;
|
|
wpa_printf(MSG_DEBUG, "FT: Postponed auth callback result for " MACSTR
|
|
" - status %u", MAC2STR(sm->addr), status);
|
|
|
|
sm->ft_pending_cb(sm->ft_pending_cb_ctx, sm->addr, sm->wpa_auth->addr,
|
|
sm->ft_pending_auth_transaction + 1, status,
|
|
resp_ies, resp_ies_len);
|
|
os_free(resp_ies);
|
|
}
|
|
|
|
|
|
struct ft_get_sta_ctx {
|
|
const u8 *nonce;
|
|
const u8 *s1kh_id;
|
|
struct wpa_state_machine *sm;
|
|
};
|
|
|
|
|
|
static int ft_get_sta_cb(struct wpa_state_machine *sm, void *ctx)
|
|
{
|
|
struct ft_get_sta_ctx *info = ctx;
|
|
|
|
if ((info->s1kh_id &&
|
|
os_memcmp(info->s1kh_id, sm->addr, ETH_ALEN) != 0) ||
|
|
os_memcmp(info->nonce, sm->ft_pending_pull_nonce,
|
|
FT_RRB_NONCE_LEN) != 0 ||
|
|
sm->ft_pending_cb == NULL || sm->ft_pending_req_ies == NULL)
|
|
return 0;
|
|
|
|
info->sm = sm;
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
static int wpa_ft_rrb_rx_resp(struct wpa_authenticator *wpa_auth,
|
|
const u8 *src_addr,
|
|
const u8 *enc, size_t enc_len,
|
|
const u8 *auth, size_t auth_len,
|
|
int no_defer)
|
|
{
|
|
const char *msgtype = "pull response";
|
|
int nak, ret = -1;
|
|
struct ft_get_sta_ctx ctx;
|
|
u8 s1kh_id[ETH_ALEN];
|
|
const u8 *f_nonce;
|
|
size_t f_nonce_len;
|
|
|
|
wpa_printf(MSG_DEBUG, "FT: Received PMK-R1 pull response");
|
|
|
|
RRB_GET_AUTH(FT_RRB_NONCE, nonce, msgtype, FT_RRB_NONCE_LEN);
|
|
wpa_hexdump(MSG_DEBUG, "FT: nonce", f_nonce, f_nonce_len);
|
|
|
|
os_memset(&ctx, 0, sizeof(ctx));
|
|
ctx.nonce = f_nonce;
|
|
if (!wpa_auth_for_each_sta(wpa_auth, ft_get_sta_cb, &ctx)) {
|
|
/* nonce not found */
|
|
wpa_printf(MSG_DEBUG, "FT: Invalid nonce");
|
|
return -1;
|
|
}
|
|
|
|
ret = wpa_ft_rrb_rx_r1(wpa_auth, src_addr, FT_PACKET_R0KH_R1KH_RESP,
|
|
enc, enc_len, auth, auth_len, msgtype, s1kh_id,
|
|
no_defer ? NULL : &wpa_ft_rrb_rx_resp);
|
|
if (ret == -2) {
|
|
ret = 0;
|
|
nak = 1;
|
|
} else {
|
|
nak = 0;
|
|
}
|
|
if (ret < 0)
|
|
return -1;
|
|
|
|
ctx.s1kh_id = s1kh_id;
|
|
if (wpa_auth_for_each_sta(wpa_auth, ft_get_sta_cb, &ctx)) {
|
|
wpa_printf(MSG_DEBUG,
|
|
"FT: Response to a pending pull request for " MACSTR,
|
|
MAC2STR(ctx.sm->addr));
|
|
eloop_cancel_timeout(wpa_ft_expire_pull, ctx.sm, NULL);
|
|
if (nak)
|
|
ctx.sm->ft_pending_pull_left_retries = 0;
|
|
ft_finish_pull(ctx.sm);
|
|
}
|
|
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int wpa_ft_rrb_rx_push(struct wpa_authenticator *wpa_auth,
|
|
const u8 *src_addr,
|
|
const u8 *enc, size_t enc_len,
|
|
const u8 *auth, size_t auth_len, int no_defer)
|
|
{
|
|
const char *msgtype = "push";
|
|
|
|
wpa_printf(MSG_DEBUG, "FT: Received PMK-R1 push");
|
|
|
|
if (wpa_ft_rrb_rx_r1(wpa_auth, src_addr, FT_PACKET_R0KH_R1KH_PUSH,
|
|
enc, enc_len, auth, auth_len, msgtype, NULL,
|
|
no_defer ? NULL : wpa_ft_rrb_rx_push) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int wpa_ft_rrb_rx_seq(struct wpa_authenticator *wpa_auth,
|
|
const u8 *src_addr, int type,
|
|
const u8 *enc, size_t enc_len,
|
|
const u8 *auth, size_t auth_len,
|
|
struct ft_remote_seq **rkh_seq,
|
|
u8 **key, size_t *key_len,
|
|
struct ft_remote_r0kh **r0kh_out,
|
|
struct ft_remote_r1kh **r1kh_out,
|
|
struct ft_remote_r0kh **r0kh_wildcard_out,
|
|
struct ft_remote_r1kh **r1kh_wildcard_out)
|
|
{
|
|
struct ft_remote_r0kh *r0kh = NULL;
|
|
struct ft_remote_r1kh *r1kh = NULL;
|
|
const u8 *f_r0kh_id, *f_r1kh_id;
|
|
size_t f_r0kh_id_len, f_r1kh_id_len;
|
|
int to_r0kh, to_r1kh;
|
|
u8 *plain = NULL;
|
|
size_t plain_len = 0;
|
|
struct ft_remote_r0kh *r0kh_wildcard;
|
|
struct ft_remote_r1kh *r1kh_wildcard;
|
|
|
|
RRB_GET_AUTH(FT_RRB_R0KH_ID, r0kh_id, "seq", -1);
|
|
RRB_GET_AUTH(FT_RRB_R1KH_ID, r1kh_id, "seq", FT_R1KH_ID_LEN);
|
|
|
|
to_r0kh = !wpa_ft_rrb_check_r0kh(wpa_auth, f_r0kh_id, f_r0kh_id_len);
|
|
to_r1kh = !wpa_ft_rrb_check_r1kh(wpa_auth, f_r1kh_id);
|
|
|
|
if (to_r0kh && to_r1kh) {
|
|
wpa_printf(MSG_DEBUG, "FT: seq - local R0KH-ID and R1KH-ID");
|
|
goto out;
|
|
}
|
|
|
|
if (!to_r0kh && !to_r1kh) {
|
|
wpa_printf(MSG_DEBUG, "FT: seq - remote R0KH-ID and R1KH-ID");
|
|
goto out;
|
|
}
|
|
|
|
if (!to_r0kh) {
|
|
wpa_ft_rrb_lookup_r0kh(wpa_auth, f_r0kh_id, f_r0kh_id_len,
|
|
&r0kh, &r0kh_wildcard);
|
|
if (!r0kh_wildcard &&
|
|
(!r0kh || os_memcmp(r0kh->addr, src_addr, ETH_ALEN) != 0)) {
|
|
wpa_hexdump(MSG_DEBUG, "FT: Did not find R0KH-ID",
|
|
f_r0kh_id, f_r0kh_id_len);
|
|
goto out;
|
|
}
|
|
if (r0kh) {
|
|
*key = r0kh->key;
|
|
*key_len = sizeof(r0kh->key);
|
|
} else {
|
|
*key = r0kh_wildcard->key;
|
|
*key_len = sizeof(r0kh_wildcard->key);
|
|
}
|
|
}
|
|
|
|
if (!to_r1kh) {
|
|
wpa_ft_rrb_lookup_r1kh(wpa_auth, f_r1kh_id, &r1kh,
|
|
&r1kh_wildcard);
|
|
if (!r1kh_wildcard &&
|
|
(!r1kh || os_memcmp(r1kh->addr, src_addr, ETH_ALEN) != 0)) {
|
|
wpa_hexdump(MSG_DEBUG, "FT: Did not find R1KH-ID",
|
|
f_r1kh_id, FT_R1KH_ID_LEN);
|
|
goto out;
|
|
}
|
|
if (r1kh) {
|
|
*key = r1kh->key;
|
|
*key_len = sizeof(r1kh->key);
|
|
} else {
|
|
*key = r1kh_wildcard->key;
|
|
*key_len = sizeof(r1kh_wildcard->key);
|
|
}
|
|
}
|
|
|
|
if (wpa_ft_rrb_decrypt(*key, *key_len, enc, enc_len, auth, auth_len,
|
|
src_addr, type, &plain, &plain_len) < 0)
|
|
goto out;
|
|
|
|
os_free(plain);
|
|
|
|
if (!to_r0kh) {
|
|
if (!r0kh)
|
|
r0kh = wpa_ft_rrb_add_r0kh(wpa_auth, r0kh_wildcard,
|
|
src_addr, f_r0kh_id,
|
|
f_r0kh_id_len,
|
|
ftRRBseqTimeout);
|
|
if (!r0kh)
|
|
goto out;
|
|
|
|
wpa_ft_rrb_r0kh_replenish(wpa_auth, r0kh, ftRRBseqTimeout);
|
|
*rkh_seq = r0kh->seq;
|
|
if (r0kh_out)
|
|
*r0kh_out = r0kh;
|
|
if (r0kh_wildcard_out)
|
|
*r0kh_wildcard_out = r0kh_wildcard;
|
|
}
|
|
|
|
if (!to_r1kh) {
|
|
if (!r1kh)
|
|
r1kh = wpa_ft_rrb_add_r1kh(wpa_auth, r1kh_wildcard,
|
|
src_addr, f_r1kh_id,
|
|
ftRRBseqTimeout);
|
|
if (!r1kh)
|
|
goto out;
|
|
|
|
wpa_ft_rrb_r1kh_replenish(wpa_auth, r1kh, ftRRBseqTimeout);
|
|
*rkh_seq = r1kh->seq;
|
|
if (r1kh_out)
|
|
*r1kh_out = r1kh;
|
|
if (r1kh_wildcard_out)
|
|
*r1kh_wildcard_out = r1kh_wildcard;
|
|
}
|
|
|
|
return 0;
|
|
out:
|
|
return -1;
|
|
}
|
|
|
|
|
|
static int wpa_ft_rrb_rx_seq_req(struct wpa_authenticator *wpa_auth,
|
|
const u8 *src_addr,
|
|
const u8 *enc, size_t enc_len,
|
|
const u8 *auth, size_t auth_len,
|
|
int no_defer)
|
|
{
|
|
int ret = -1;
|
|
struct ft_rrb_seq f_seq;
|
|
const u8 *f_nonce, *f_r0kh_id, *f_r1kh_id;
|
|
size_t f_nonce_len, f_r0kh_id_len, f_r1kh_id_len;
|
|
struct ft_remote_seq *rkh_seq = NULL;
|
|
u8 *packet = NULL, *key = NULL;
|
|
size_t packet_len = 0, key_len = 0;
|
|
struct tlv_list seq_resp_auth[5];
|
|
|
|
wpa_printf(MSG_DEBUG, "FT: Received sequence number request");
|
|
|
|
if (wpa_ft_rrb_rx_seq(wpa_auth, src_addr, FT_PACKET_R0KH_R1KH_SEQ_REQ,
|
|
enc, enc_len, auth, auth_len, &rkh_seq, &key,
|
|
&key_len, NULL, NULL, NULL, NULL) < 0)
|
|
goto out;
|
|
|
|
RRB_GET_AUTH(FT_RRB_NONCE, nonce, "seq request", FT_RRB_NONCE_LEN);
|
|
wpa_hexdump(MSG_DEBUG, "FT: seq request - nonce", f_nonce, f_nonce_len);
|
|
|
|
RRB_GET_AUTH(FT_RRB_R0KH_ID, r0kh_id, "seq", -1);
|
|
RRB_GET_AUTH(FT_RRB_R1KH_ID, r1kh_id, "seq", FT_R1KH_ID_LEN);
|
|
|
|
if (wpa_ft_new_seq(rkh_seq, &f_seq) < 0) {
|
|
wpa_printf(MSG_DEBUG, "FT: Failed to get seq num");
|
|
goto out;
|
|
}
|
|
|
|
wpa_printf(MSG_DEBUG, "FT: Send sequence number response from " MACSTR
|
|
" to " MACSTR,
|
|
MAC2STR(wpa_auth->addr), MAC2STR(src_addr));
|
|
|
|
seq_resp_auth[0].type = FT_RRB_NONCE;
|
|
seq_resp_auth[0].len = f_nonce_len;
|
|
seq_resp_auth[0].data = f_nonce;
|
|
seq_resp_auth[1].type = FT_RRB_SEQ;
|
|
seq_resp_auth[1].len = sizeof(f_seq);
|
|
seq_resp_auth[1].data = (u8 *) &f_seq;
|
|
seq_resp_auth[2].type = FT_RRB_R0KH_ID;
|
|
seq_resp_auth[2].len = f_r0kh_id_len;
|
|
seq_resp_auth[2].data = f_r0kh_id;
|
|
seq_resp_auth[3].type = FT_RRB_R1KH_ID;
|
|
seq_resp_auth[3].len = FT_R1KH_ID_LEN;
|
|
seq_resp_auth[3].data = f_r1kh_id;
|
|
seq_resp_auth[4].type = FT_RRB_LAST_EMPTY;
|
|
seq_resp_auth[4].len = 0;
|
|
seq_resp_auth[4].data = NULL;
|
|
|
|
if (wpa_ft_rrb_build(key, key_len, NULL, NULL, seq_resp_auth, NULL,
|
|
wpa_auth->addr, FT_PACKET_R0KH_R1KH_SEQ_RESP,
|
|
&packet, &packet_len) < 0)
|
|
goto out;
|
|
|
|
wpa_ft_rrb_oui_send(wpa_auth, src_addr,
|
|
FT_PACKET_R0KH_R1KH_SEQ_RESP, packet,
|
|
packet_len);
|
|
|
|
out:
|
|
os_free(packet);
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int wpa_ft_rrb_rx_seq_resp(struct wpa_authenticator *wpa_auth,
|
|
const u8 *src_addr,
|
|
const u8 *enc, size_t enc_len,
|
|
const u8 *auth, size_t auth_len,
|
|
int no_defer)
|
|
{
|
|
u8 *key = NULL;
|
|
size_t key_len = 0;
|
|
struct ft_remote_r0kh *r0kh = NULL, *r0kh_wildcard = NULL;
|
|
struct ft_remote_r1kh *r1kh = NULL, *r1kh_wildcard = NULL;
|
|
const u8 *f_nonce, *f_seq;
|
|
size_t f_nonce_len, f_seq_len;
|
|
struct ft_remote_seq *rkh_seq = NULL;
|
|
struct ft_remote_item *item;
|
|
struct os_reltime now, now_remote;
|
|
int seq_ret, found;
|
|
const struct ft_rrb_seq *msg_both;
|
|
u32 msg_dom, msg_seq;
|
|
|
|
wpa_printf(MSG_DEBUG, "FT: Received sequence number response");
|
|
|
|
if (wpa_ft_rrb_rx_seq(wpa_auth, src_addr, FT_PACKET_R0KH_R1KH_SEQ_RESP,
|
|
enc, enc_len, auth, auth_len, &rkh_seq, &key,
|
|
&key_len, &r0kh, &r1kh, &r0kh_wildcard,
|
|
&r1kh_wildcard) < 0)
|
|
goto out;
|
|
|
|
RRB_GET_AUTH(FT_RRB_NONCE, nonce, "seq response", FT_RRB_NONCE_LEN);
|
|
wpa_hexdump(MSG_DEBUG, "FT: seq response - nonce", f_nonce,
|
|
f_nonce_len);
|
|
|
|
found = 0;
|
|
dl_list_for_each(item, &rkh_seq->rx.queue, struct ft_remote_item,
|
|
list) {
|
|
if (os_memcmp_const(f_nonce, item->nonce,
|
|
FT_RRB_NONCE_LEN) != 0 ||
|
|
os_get_reltime(&now) < 0 ||
|
|
os_reltime_expired(&now, &item->nonce_ts, ftRRBseqTimeout))
|
|
continue;
|
|
|
|
found = 1;
|
|
break;
|
|
}
|
|
if (!found) {
|
|
wpa_printf(MSG_DEBUG, "FT: seq response - bad nonce");
|
|
goto out;
|
|
}
|
|
|
|
if (r0kh) {
|
|
wpa_ft_rrb_r0kh_replenish(wpa_auth, r0kh,
|
|
wpa_auth->conf.rkh_pos_timeout);
|
|
if (r0kh_wildcard)
|
|
os_memcpy(r0kh->addr, src_addr, ETH_ALEN);
|
|
}
|
|
|
|
if (r1kh) {
|
|
wpa_ft_rrb_r1kh_replenish(wpa_auth, r1kh,
|
|
wpa_auth->conf.rkh_pos_timeout);
|
|
if (r1kh_wildcard)
|
|
os_memcpy(r1kh->addr, src_addr, ETH_ALEN);
|
|
}
|
|
|
|
seq_ret = wpa_ft_rrb_seq_chk(rkh_seq, src_addr, enc, enc_len, auth,
|
|
auth_len, "seq response", 1);
|
|
if (seq_ret == FT_RRB_SEQ_OK) {
|
|
wpa_printf(MSG_DEBUG, "FT: seq response - valid seq number");
|
|
wpa_ft_rrb_seq_accept(wpa_auth, rkh_seq, src_addr, auth,
|
|
auth_len, "seq response");
|
|
} else {
|
|
wpa_printf(MSG_DEBUG, "FT: seq response - reset seq number");
|
|
|
|
RRB_GET_AUTH(FT_RRB_SEQ, seq, "seq response",
|
|
sizeof(*msg_both));
|
|
msg_both = (const struct ft_rrb_seq *) f_seq;
|
|
|
|
msg_dom = le_to_host32(msg_both->dom);
|
|
msg_seq = le_to_host32(msg_both->seq);
|
|
now_remote.sec = le_to_host32(msg_both->ts);
|
|
now_remote.usec = 0;
|
|
|
|
rkh_seq->rx.num_last = 2;
|
|
rkh_seq->rx.dom = msg_dom;
|
|
rkh_seq->rx.offsetidx = 0;
|
|
/* Accept some older, possibly cached packets as well */
|
|
rkh_seq->rx.last[0] = msg_seq - FT_REMOTE_SEQ_BACKLOG -
|
|
dl_list_len(&rkh_seq->rx.queue);
|
|
rkh_seq->rx.last[1] = msg_seq;
|
|
|
|
/* local time - offset = remote time
|
|
* <=> local time - remote time = offset */
|
|
os_reltime_sub(&now, &now_remote, &rkh_seq->rx.time_offset);
|
|
}
|
|
|
|
wpa_ft_rrb_seq_flush(wpa_auth, rkh_seq, 1);
|
|
|
|
return 0;
|
|
out:
|
|
return -1;
|
|
}
|
|
|
|
|
|
int wpa_ft_rrb_rx(struct wpa_authenticator *wpa_auth, const u8 *src_addr,
|
|
const u8 *data, size_t data_len)
|
|
{
|
|
struct ft_rrb_frame *frame;
|
|
u16 alen;
|
|
const u8 *pos, *end, *start;
|
|
u8 action;
|
|
const u8 *sta_addr, *target_ap_addr;
|
|
|
|
wpa_printf(MSG_DEBUG, "FT: RRB received frame from remote AP " MACSTR,
|
|
MAC2STR(src_addr));
|
|
|
|
if (data_len < sizeof(*frame)) {
|
|
wpa_printf(MSG_DEBUG, "FT: Too short RRB frame (data_len=%lu)",
|
|
(unsigned long) data_len);
|
|
return -1;
|
|
}
|
|
|
|
pos = data;
|
|
frame = (struct ft_rrb_frame *) pos;
|
|
pos += sizeof(*frame);
|
|
|
|
alen = le_to_host16(frame->action_length);
|
|
wpa_printf(MSG_DEBUG, "FT: RRB frame - frame_type=%d packet_type=%d "
|
|
"action_length=%d ap_address=" MACSTR,
|
|
frame->frame_type, frame->packet_type, alen,
|
|
MAC2STR(frame->ap_address));
|
|
|
|
if (frame->frame_type != RSN_REMOTE_FRAME_TYPE_FT_RRB) {
|
|
/* Discard frame per IEEE Std 802.11r-2008, 11A.10.3 */
|
|
wpa_printf(MSG_DEBUG, "FT: RRB discarded frame with "
|
|
"unrecognized type %d", frame->frame_type);
|
|
return -1;
|
|
}
|
|
|
|
if (alen > data_len - sizeof(*frame)) {
|
|
wpa_printf(MSG_DEBUG, "FT: RRB frame too short for action "
|
|
"frame");
|
|
return -1;
|
|
}
|
|
|
|
wpa_hexdump(MSG_MSGDUMP, "FT: RRB - FT Action frame", pos, alen);
|
|
|
|
if (alen < 1 + 1 + 2 * ETH_ALEN) {
|
|
wpa_printf(MSG_DEBUG, "FT: Too short RRB frame (not enough "
|
|
"room for Action Frame body); alen=%lu",
|
|
(unsigned long) alen);
|
|
return -1;
|
|
}
|
|
start = pos;
|
|
end = pos + alen;
|
|
|
|
if (*pos != WLAN_ACTION_FT) {
|
|
wpa_printf(MSG_DEBUG, "FT: Unexpected Action frame category "
|
|
"%d", *pos);
|
|
return -1;
|
|
}
|
|
|
|
pos++;
|
|
action = *pos++;
|
|
sta_addr = pos;
|
|
pos += ETH_ALEN;
|
|
target_ap_addr = pos;
|
|
pos += ETH_ALEN;
|
|
wpa_printf(MSG_DEBUG, "FT: RRB Action Frame: action=%d sta_addr="
|
|
MACSTR " target_ap_addr=" MACSTR,
|
|
action, MAC2STR(sta_addr), MAC2STR(target_ap_addr));
|
|
|
|
if (frame->packet_type == FT_PACKET_REQUEST) {
|
|
wpa_printf(MSG_DEBUG, "FT: FT Packet Type - Request");
|
|
|
|
if (action != 1) {
|
|
wpa_printf(MSG_DEBUG, "FT: Unexpected Action %d in "
|
|
"RRB Request", action);
|
|
return -1;
|
|
}
|
|
|
|
if (os_memcmp(target_ap_addr, wpa_auth->addr, ETH_ALEN) != 0) {
|
|
wpa_printf(MSG_DEBUG, "FT: Target AP address in the "
|
|
"RRB Request does not match with own "
|
|
"address");
|
|
return -1;
|
|
}
|
|
|
|
if (wpa_ft_rrb_rx_request(wpa_auth, frame->ap_address,
|
|
sta_addr, pos, end - pos) < 0)
|
|
return -1;
|
|
} else if (frame->packet_type == FT_PACKET_RESPONSE) {
|
|
u16 status_code;
|
|
|
|
if (end - pos < 2) {
|
|
wpa_printf(MSG_DEBUG, "FT: Not enough room for status "
|
|
"code in RRB Response");
|
|
return -1;
|
|
}
|
|
status_code = WPA_GET_LE16(pos);
|
|
|
|
wpa_printf(MSG_DEBUG, "FT: FT Packet Type - Response "
|
|
"(status_code=%d)", status_code);
|
|
|
|
if (wpa_ft_action_send(wpa_auth, sta_addr, start, alen) < 0)
|
|
return -1;
|
|
} else {
|
|
wpa_printf(MSG_DEBUG, "FT: RRB discarded frame with unknown "
|
|
"packet_type %d", frame->packet_type);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
void wpa_ft_rrb_oui_rx(struct wpa_authenticator *wpa_auth, const u8 *src_addr,
|
|
const u8 *dst_addr, u8 oui_suffix, const u8 *data,
|
|
size_t data_len)
|
|
{
|
|
const u8 *auth, *enc;
|
|
size_t alen, elen;
|
|
int no_defer = 0;
|
|
|
|
wpa_printf(MSG_DEBUG, "FT: RRB-OUI(" MACSTR
|
|
") received frame from remote AP "
|
|
MACSTR " oui_suffix=%u dst=" MACSTR,
|
|
MAC2STR(wpa_auth->addr), MAC2STR(src_addr), oui_suffix,
|
|
MAC2STR(dst_addr));
|
|
wpa_hexdump(MSG_MSGDUMP, "FT: RRB frame payload", data, data_len);
|
|
|
|
if (is_multicast_ether_addr(src_addr)) {
|
|
wpa_printf(MSG_DEBUG,
|
|
"FT: RRB-OUI received frame from multicast address "
|
|
MACSTR, MAC2STR(src_addr));
|
|
return;
|
|
}
|
|
|
|
if (is_multicast_ether_addr(dst_addr))
|
|
no_defer = 1;
|
|
|
|
if (data_len < sizeof(u16)) {
|
|
wpa_printf(MSG_DEBUG, "FT: RRB-OUI frame too short");
|
|
return;
|
|
}
|
|
|
|
alen = WPA_GET_LE16(data);
|
|
if (data_len < sizeof(u16) + alen) {
|
|
wpa_printf(MSG_DEBUG, "FT: RRB-OUI frame too short");
|
|
return;
|
|
}
|
|
|
|
auth = data + sizeof(u16);
|
|
wpa_hexdump(MSG_MSGDUMP, "FT: Authenticated payload", auth, alen);
|
|
enc = data + sizeof(u16) + alen;
|
|
elen = data_len - sizeof(u16) - alen;
|
|
wpa_hexdump(MSG_MSGDUMP, "FT: Encrypted payload", enc, elen);
|
|
|
|
switch (oui_suffix) {
|
|
case FT_PACKET_R0KH_R1KH_PULL:
|
|
wpa_ft_rrb_rx_pull(wpa_auth, src_addr, enc, elen, auth, alen,
|
|
no_defer);
|
|
break;
|
|
case FT_PACKET_R0KH_R1KH_RESP:
|
|
wpa_ft_rrb_rx_resp(wpa_auth, src_addr, enc, elen, auth, alen,
|
|
no_defer);
|
|
break;
|
|
case FT_PACKET_R0KH_R1KH_PUSH:
|
|
wpa_ft_rrb_rx_push(wpa_auth, src_addr, enc, elen, auth, alen,
|
|
no_defer);
|
|
break;
|
|
case FT_PACKET_R0KH_R1KH_SEQ_REQ:
|
|
wpa_ft_rrb_rx_seq_req(wpa_auth, src_addr, enc, elen, auth, alen,
|
|
no_defer);
|
|
break;
|
|
case FT_PACKET_R0KH_R1KH_SEQ_RESP:
|
|
wpa_ft_rrb_rx_seq_resp(wpa_auth, src_addr, enc, elen, auth,
|
|
alen, no_defer);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
static int wpa_ft_generate_pmk_r1(struct wpa_authenticator *wpa_auth,
|
|
struct wpa_ft_pmk_r0_sa *pmk_r0,
|
|
struct ft_remote_r1kh *r1kh,
|
|
const u8 *s1kh_id)
|
|
{
|
|
u8 *packet;
|
|
size_t packet_len;
|
|
struct ft_rrb_seq f_seq;
|
|
struct tlv_list push[] = {
|
|
{ .type = FT_RRB_S1KH_ID, .len = ETH_ALEN,
|
|
.data = s1kh_id },
|
|
{ .type = FT_RRB_PMK_R0_NAME, .len = WPA_PMK_NAME_LEN,
|
|
.data = pmk_r0->pmk_r0_name },
|
|
{ .type = FT_RRB_LAST_EMPTY, .len = 0, .data = NULL },
|
|
};
|
|
struct tlv_list push_auth[] = {
|
|
{ .type = FT_RRB_SEQ, .len = sizeof(f_seq),
|
|
.data = (u8 *) &f_seq },
|
|
{ .type = FT_RRB_R0KH_ID,
|
|
.len = wpa_auth->conf.r0_key_holder_len,
|
|
.data = wpa_auth->conf.r0_key_holder },
|
|
{ .type = FT_RRB_R1KH_ID, .len = FT_R1KH_ID_LEN,
|
|
.data = r1kh->id },
|
|
{ .type = FT_RRB_LAST_EMPTY, .len = 0, .data = NULL },
|
|
};
|
|
|
|
if (wpa_ft_new_seq(r1kh->seq, &f_seq) < 0) {
|
|
wpa_printf(MSG_DEBUG, "FT: Failed to get seq num");
|
|
return -1;
|
|
}
|
|
|
|
wpa_printf(MSG_DEBUG, "FT: Send PMK-R1 push from " MACSTR
|
|
" to remote R0KH address " MACSTR,
|
|
MAC2STR(wpa_auth->addr), MAC2STR(r1kh->addr));
|
|
|
|
if (wpa_ft_rrb_build_r0(r1kh->key, sizeof(r1kh->key), push, pmk_r0,
|
|
r1kh->id, s1kh_id, push_auth, wpa_auth->addr,
|
|
FT_PACKET_R0KH_R1KH_PUSH,
|
|
&packet, &packet_len) < 0)
|
|
return -1;
|
|
|
|
wpa_ft_rrb_oui_send(wpa_auth, r1kh->addr, FT_PACKET_R0KH_R1KH_PUSH,
|
|
packet, packet_len);
|
|
|
|
os_free(packet);
|
|
return 0;
|
|
}
|
|
|
|
|
|
void wpa_ft_push_pmk_r1(struct wpa_authenticator *wpa_auth, const u8 *addr)
|
|
{
|
|
struct wpa_ft_pmk_cache *cache = wpa_auth->ft_pmk_cache;
|
|
struct wpa_ft_pmk_r0_sa *r0, *r0found = NULL;
|
|
struct ft_remote_r1kh *r1kh;
|
|
|
|
if (!wpa_auth->conf.pmk_r1_push)
|
|
return;
|
|
if (!wpa_auth->conf.r1kh_list)
|
|
return;
|
|
|
|
dl_list_for_each(r0, &cache->pmk_r0, struct wpa_ft_pmk_r0_sa, list) {
|
|
if (os_memcmp(r0->spa, addr, ETH_ALEN) == 0) {
|
|
r0found = r0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
r0 = r0found;
|
|
if (r0 == NULL || r0->pmk_r1_pushed)
|
|
return;
|
|
r0->pmk_r1_pushed = 1;
|
|
|
|
wpa_printf(MSG_DEBUG, "FT: Deriving and pushing PMK-R1 keys to R1KHs "
|
|
"for STA " MACSTR, MAC2STR(addr));
|
|
|
|
for (r1kh = *wpa_auth->conf.r1kh_list; r1kh; r1kh = r1kh->next) {
|
|
if (is_zero_ether_addr(r1kh->addr) ||
|
|
is_zero_ether_addr(r1kh->id))
|
|
continue;
|
|
if (wpa_ft_rrb_init_r1kh_seq(r1kh) < 0)
|
|
continue;
|
|
wpa_ft_generate_pmk_r1(wpa_auth, r0, r1kh, addr);
|
|
}
|
|
}
|
|
|
|
#endif /* CONFIG_IEEE80211R_AP */
|