mirror of
https://git.kernel.org/pub/scm/network/wireless/iwd.git
synced 2024-11-22 23:09:34 +01:00
2724 lines
61 KiB
C
2724 lines
61 KiB
C
/*
|
|
*
|
|
* Wireless daemon for Linux
|
|
*
|
|
* Copyright (C) 2013-2019 Intel Corporation. All rights reserved.
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include <config.h>
|
|
#endif
|
|
|
|
#include <errno.h>
|
|
#include <arpa/inet.h>
|
|
|
|
#include <ell/ell.h>
|
|
|
|
#include "ell/useful.h"
|
|
#include "src/util.h"
|
|
#include "src/crypto.h"
|
|
#include "src/ie.h"
|
|
|
|
const unsigned char ieee_oui[3] = { 0x00, 0x0f, 0xac };
|
|
const unsigned char microsoft_oui[3] = { 0x00, 0x50, 0xf2 };
|
|
const unsigned char wifi_alliance_oui[3] = { 0x50, 0x6f, 0x9a };
|
|
|
|
void ie_tlv_iter_init(struct ie_tlv_iter *iter, const unsigned char *tlv,
|
|
unsigned int len)
|
|
{
|
|
iter->tlv = tlv;
|
|
iter->max = len;
|
|
iter->pos = 0;
|
|
}
|
|
|
|
void ie_tlv_iter_recurse(struct ie_tlv_iter *iter,
|
|
struct ie_tlv_iter *recurse)
|
|
{
|
|
recurse->tlv = iter->data;
|
|
recurse->max = iter->len;
|
|
recurse->pos = 0;
|
|
}
|
|
|
|
bool ie_tlv_iter_next(struct ie_tlv_iter *iter)
|
|
{
|
|
const unsigned char *tlv = iter->tlv + iter->pos;
|
|
const unsigned char *end = iter->tlv + iter->max;
|
|
unsigned int tag;
|
|
unsigned int len;
|
|
|
|
if (iter->pos + 1 >= iter->max)
|
|
return false;
|
|
|
|
tag = *tlv++;
|
|
len = *tlv++;
|
|
|
|
if (tag == IE_TYPE_EXTENSION) {
|
|
if (iter->pos + 2 >= iter->max || len < 1)
|
|
return false;
|
|
|
|
tag = 256 + *tlv++;
|
|
len--;
|
|
}
|
|
|
|
if (tlv + len > end)
|
|
return false;
|
|
|
|
iter->tag = tag;
|
|
iter->len = len;
|
|
iter->data = tlv;
|
|
|
|
iter->pos = tlv + len - iter->tlv;
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Concatenate all vendor IEs with a given OUI + type.
|
|
*
|
|
* Returns a newly allocated buffer with the contents of the matching ies
|
|
* copied into it. @out_len is set to the overall size of the contents.
|
|
* If no matching elements were found, NULL is returned and @out_len is
|
|
* set to -ENOENT.
|
|
*/
|
|
static void *ie_tlv_vendor_ie_concat(const unsigned char oui[],
|
|
unsigned char type,
|
|
const unsigned char *ies,
|
|
unsigned int len,
|
|
bool empty_ok,
|
|
ssize_t *out_len)
|
|
{
|
|
struct ie_tlv_iter iter;
|
|
const unsigned char *data;
|
|
unsigned int ie_len;
|
|
unsigned int concat_len = 0;
|
|
unsigned char *ret;
|
|
bool ie_found = false;
|
|
|
|
ie_tlv_iter_init(&iter, ies, len);
|
|
|
|
while (ie_tlv_iter_next(&iter)) {
|
|
if (ie_tlv_iter_get_tag(&iter) != IE_TYPE_VENDOR_SPECIFIC)
|
|
continue;
|
|
|
|
ie_len = ie_tlv_iter_get_length(&iter);
|
|
if (ie_len < 4)
|
|
continue;
|
|
|
|
data = ie_tlv_iter_get_data(&iter);
|
|
|
|
if (memcmp(data, oui, 3))
|
|
continue;
|
|
|
|
if (data[3] != type)
|
|
continue;
|
|
|
|
concat_len += ie_len - 4;
|
|
ie_found = true;
|
|
}
|
|
|
|
if (concat_len == 0) {
|
|
if (out_len)
|
|
*out_len = (ie_found && empty_ok) ? 0 : -ENOENT;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
ie_tlv_iter_init(&iter, ies, len);
|
|
ret = l_malloc(concat_len);
|
|
|
|
concat_len = 0;
|
|
|
|
while (ie_tlv_iter_next(&iter)) {
|
|
if (ie_tlv_iter_get_tag(&iter) != IE_TYPE_VENDOR_SPECIFIC)
|
|
continue;
|
|
|
|
ie_len = ie_tlv_iter_get_length(&iter);
|
|
if (ie_len < 4)
|
|
continue;
|
|
|
|
data = ie_tlv_iter_get_data(&iter);
|
|
|
|
if (memcmp(data, oui, 3))
|
|
continue;
|
|
|
|
if (data[3] != type)
|
|
continue;
|
|
|
|
memcpy(ret + concat_len, data + 4, ie_len - 4);
|
|
concat_len += ie_len - 4;
|
|
}
|
|
|
|
if (out_len)
|
|
*out_len = concat_len;
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Wi-Fi Simple Configuration v2.0.5, Section 8.2:
|
|
* "There may be more than one instance of the Wi-Fi Simple Configuration
|
|
* Information Element in a single 802.11 management frame. If multiple
|
|
* Information Elements are present, the Wi-Fi Simple Configuration data
|
|
* consists of the concatenation of the Data components of those Information
|
|
* Elements (the order of these elements in the original packet shall be
|
|
* preserved when concatenating Data components)."
|
|
*/
|
|
void *ie_tlv_extract_wsc_payload(const unsigned char *ies, size_t len,
|
|
ssize_t *out_len)
|
|
{
|
|
return ie_tlv_vendor_ie_concat(microsoft_oui, 0x04,
|
|
ies, len, false, out_len);
|
|
}
|
|
|
|
/*
|
|
* Wi-Fi P2P Technical Specification v1.7, Section 8.2:
|
|
* "More than one P2P IE may be included in a single frame. If multiple P2P
|
|
* IEs are present, the complete P2P attribute data consists of the
|
|
* concatenation of the P2P Attribute fields of the P2P IEs. The P2P
|
|
* Attributes field of each P2P IE may be any length up to the maximum
|
|
* (251 octets). The order of the concatenated P2P attribute data shall be
|
|
* preserved in the ordering of the P2P IEs in the frame. All of the P2P IEs
|
|
* shall fit within a single frame and shall be adjacent in the frame."
|
|
*/
|
|
void *ie_tlv_extract_p2p_payload(const unsigned char *ies, size_t len,
|
|
ssize_t *out_len)
|
|
{
|
|
return ie_tlv_vendor_ie_concat(wifi_alliance_oui, 0x09,
|
|
ies, len, true, out_len);
|
|
}
|
|
|
|
/*
|
|
* Wi-Fi Display Technical Specification v2.1.0, Section 5.1.1:
|
|
* "More than one WFD IE may be included in a single frame. If multiple WFD
|
|
* IEs are present, the complete WFD subelement data consists of the
|
|
* concatenation of the WFD subelement fields of the WFD IEs. The WFD
|
|
* subelements field of each WFD IE may be any length up to the maximum
|
|
* (251 octets). The order of the concatenated WFD subelement data shall be
|
|
* preserved in the ordering of the WFD IEs in the frame. All of the WFD IEs
|
|
* shall fit within a single frame and shall be adjacent in the frame."
|
|
*/
|
|
void *ie_tlv_extract_wfd_payload(const unsigned char *ies, size_t len,
|
|
ssize_t *out_len)
|
|
{
|
|
return ie_tlv_vendor_ie_concat(wifi_alliance_oui, 0x0a,
|
|
ies, len, true, out_len);
|
|
}
|
|
|
|
/*
|
|
* Encapsulate & Fragment data into Vendor IE with a given OUI + type
|
|
*
|
|
* Returns a newly allocated buffer with the contents of encapsulated into
|
|
* multiple vendor IE. @out_len is set to the overall size of the contents.
|
|
*/
|
|
static void *ie_tlv_vendor_ie_encapsulate(const unsigned char oui[],
|
|
uint8_t type,
|
|
const void *data, size_t len,
|
|
bool build_empty,
|
|
size_t *out_len)
|
|
{
|
|
size_t overhead;
|
|
size_t ie_len;
|
|
size_t offset;
|
|
uint8_t *ret;
|
|
|
|
/*
|
|
* Each Vendor IE can contain up to 251 bytes of data.
|
|
* 255 byte maximum length - 3 for oui and 1 for type
|
|
*/
|
|
overhead = (len + 250) / 251 * 6;
|
|
|
|
if (len == 0 && build_empty)
|
|
overhead = 6;
|
|
|
|
ret = l_malloc(len + overhead);
|
|
|
|
if (out_len)
|
|
*out_len = len + overhead;
|
|
|
|
offset = 0;
|
|
|
|
while (overhead) {
|
|
ie_len = len <= 251 ? len : 251;
|
|
ret[offset++] = IE_TYPE_VENDOR_SPECIFIC;
|
|
ret[offset++] = ie_len + 4;
|
|
memcpy(ret + offset, oui, 3);
|
|
offset += 3;
|
|
ret[offset++] = type;
|
|
memcpy(ret + offset, data, ie_len);
|
|
|
|
data += ie_len;
|
|
len -= ie_len;
|
|
overhead -= 6;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
void *ie_tlv_encapsulate_wsc_payload(const uint8_t *data, size_t len,
|
|
size_t *out_len)
|
|
{
|
|
return ie_tlv_vendor_ie_encapsulate(microsoft_oui, 0x04,
|
|
data, len, false, out_len);
|
|
}
|
|
|
|
void *ie_tlv_encapsulate_p2p_payload(const uint8_t *data, size_t len,
|
|
size_t *out_len)
|
|
{
|
|
return ie_tlv_vendor_ie_encapsulate(wifi_alliance_oui, 0x09,
|
|
data, len, true, out_len);
|
|
}
|
|
|
|
#define TLV_HEADER_LEN 2
|
|
|
|
static bool ie_tlv_builder_init_recurse(struct ie_tlv_builder *builder,
|
|
unsigned char *tlv, unsigned int size)
|
|
{
|
|
if (!builder)
|
|
return false;
|
|
|
|
if (!tlv) {
|
|
memset(builder->buf, 0, MAX_BUILDER_SIZE);
|
|
builder->tlv = builder->buf;
|
|
builder->max = MAX_BUILDER_SIZE;
|
|
} else {
|
|
builder->tlv = tlv;
|
|
builder->max = size;
|
|
}
|
|
|
|
builder->pos = 0;
|
|
builder->parent = NULL;
|
|
builder->tag = 0xffff;
|
|
builder->len = 0;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ie_tlv_builder_init(struct ie_tlv_builder *builder, unsigned char *buf,
|
|
size_t len)
|
|
{
|
|
return ie_tlv_builder_init_recurse(builder, buf, len);
|
|
}
|
|
|
|
static void ie_tlv_builder_write_header(struct ie_tlv_builder *builder)
|
|
{
|
|
unsigned char *tlv = builder->tlv + builder->pos;
|
|
|
|
if (builder->tag < 256) {
|
|
tlv[0] = builder->tag;
|
|
tlv[1] = builder->len;
|
|
} else {
|
|
tlv[0] = IE_TYPE_EXTENSION;
|
|
tlv[1] = builder->len + 1;
|
|
tlv[2] = builder->tag - 256;
|
|
}
|
|
}
|
|
|
|
bool ie_tlv_builder_set_length(struct ie_tlv_builder *builder,
|
|
unsigned int new_len)
|
|
{
|
|
unsigned int new_pos = builder->pos + TLV_HEADER_LEN + new_len;
|
|
|
|
if (builder->tag >= 256)
|
|
new_pos += 1;
|
|
|
|
if (new_pos > builder->max)
|
|
return false;
|
|
|
|
if (builder->parent)
|
|
ie_tlv_builder_set_length(builder->parent, new_pos);
|
|
|
|
builder->len = new_len;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ie_tlv_builder_next(struct ie_tlv_builder *builder, unsigned int new_tag)
|
|
{
|
|
if (new_tag > 0x1ff)
|
|
return false;
|
|
|
|
if (builder->tag != 0xffff) {
|
|
ie_tlv_builder_write_header(builder);
|
|
builder->pos += TLV_HEADER_LEN + builder->tlv[builder->pos + 1];
|
|
}
|
|
|
|
builder->tag = new_tag;
|
|
|
|
return ie_tlv_builder_set_length(builder, 0);
|
|
}
|
|
|
|
unsigned char *ie_tlv_builder_get_data(struct ie_tlv_builder *builder)
|
|
{
|
|
return builder->tlv + TLV_HEADER_LEN + builder->pos +
|
|
(builder->tag >= 256 ? 1 : 0);
|
|
}
|
|
|
|
bool ie_tlv_builder_set_data(struct ie_tlv_builder *builder,
|
|
const void *data, size_t len)
|
|
{
|
|
if (!ie_tlv_builder_set_length(builder, len))
|
|
return false;
|
|
|
|
memcpy(ie_tlv_builder_get_data(builder), data, len);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ie_tlv_builder_recurse(struct ie_tlv_builder *builder,
|
|
struct ie_tlv_builder *recurse)
|
|
{
|
|
unsigned char *end = builder->buf + builder->max;
|
|
unsigned char *data = ie_tlv_builder_get_data(builder);
|
|
|
|
if (!ie_tlv_builder_init_recurse(recurse, data, end - data))
|
|
return false;
|
|
|
|
recurse->parent = builder;
|
|
|
|
return true;
|
|
}
|
|
|
|
unsigned char *ie_tlv_builder_finalize(struct ie_tlv_builder *builder,
|
|
size_t *out_len)
|
|
{
|
|
unsigned int len = 0;
|
|
|
|
if (builder->tag != 0xffff) {
|
|
ie_tlv_builder_write_header(builder);
|
|
|
|
len = builder->pos + TLV_HEADER_LEN +
|
|
builder->tlv[builder->pos + 1];
|
|
}
|
|
|
|
if (out_len)
|
|
*out_len = len;
|
|
|
|
return builder->tlv;
|
|
}
|
|
|
|
/*
|
|
* Converts RSN cipher suite into an unsigned integer suitable to be used
|
|
* by nl80211. The enumeration is the same as found in crypto.h
|
|
*
|
|
* If the suite value is invalid, this function returns 0.
|
|
*/
|
|
uint32_t ie_rsn_cipher_suite_to_cipher(enum ie_rsn_cipher_suite suite)
|
|
{
|
|
switch (suite) {
|
|
case IE_RSN_CIPHER_SUITE_CCMP:
|
|
return CRYPTO_CIPHER_CCMP;
|
|
case IE_RSN_CIPHER_SUITE_TKIP:
|
|
return CRYPTO_CIPHER_TKIP;
|
|
case IE_RSN_CIPHER_SUITE_WEP40:
|
|
return CRYPTO_CIPHER_WEP40;
|
|
case IE_RSN_CIPHER_SUITE_WEP104:
|
|
return CRYPTO_CIPHER_WEP104;
|
|
case IE_RSN_CIPHER_SUITE_BIP_CMAC:
|
|
return CRYPTO_CIPHER_BIP_CMAC;
|
|
case IE_RSN_CIPHER_SUITE_GCMP:
|
|
return CRYPTO_CIPHER_GCMP;
|
|
case IE_RSN_CIPHER_SUITE_GCMP_256:
|
|
return CRYPTO_CIPHER_GCMP_256;
|
|
case IE_RSN_CIPHER_SUITE_CCMP_256:
|
|
return CRYPTO_CIPHER_CCMP_256;
|
|
case IE_RSN_CIPHER_SUITE_BIP_GMAC:
|
|
return CRYPTO_CIPHER_BIP_GMAC;
|
|
case IE_RSN_CIPHER_SUITE_BIP_GMAC_256:
|
|
return CRYPTO_CIPHER_BIP_GMAC_256;
|
|
case IE_RSN_CIPHER_SUITE_BIP_CMAC_256:
|
|
return CRYPTO_CIPHER_BIP_CMAC_256;
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
const char *ie_rsn_cipher_suite_to_string(enum ie_rsn_cipher_suite suite)
|
|
{
|
|
switch (suite) {
|
|
case IE_RSN_CIPHER_SUITE_CCMP:
|
|
return "CCMP-128";
|
|
case IE_RSN_CIPHER_SUITE_TKIP:
|
|
return "TKIP";
|
|
case IE_RSN_CIPHER_SUITE_WEP40:
|
|
return "WEP-40";
|
|
case IE_RSN_CIPHER_SUITE_WEP104:
|
|
return "WEP-104";
|
|
case IE_RSN_CIPHER_SUITE_BIP_CMAC:
|
|
return "BIP-CMAC-128";
|
|
case IE_RSN_CIPHER_SUITE_GCMP:
|
|
return "GCMP-128";
|
|
case IE_RSN_CIPHER_SUITE_GCMP_256:
|
|
return "GCMP-256";
|
|
case IE_RSN_CIPHER_SUITE_CCMP_256:
|
|
return "CCMP-256";
|
|
case IE_RSN_CIPHER_SUITE_NO_GROUP_TRAFFIC:
|
|
return "NO-TRAFFIC";
|
|
case IE_RSN_CIPHER_SUITE_USE_GROUP_CIPHER:
|
|
break;
|
|
case IE_RSN_CIPHER_SUITE_BIP_GMAC:
|
|
return "BIP-GMAC-128";
|
|
case IE_RSN_CIPHER_SUITE_BIP_GMAC_256:
|
|
return "BIP-GMAC-256";
|
|
case IE_RSN_CIPHER_SUITE_BIP_CMAC_256:
|
|
return "BIP-CMAC-256";
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/* 802.11, Section 8.4.2.27.2 */
|
|
static bool ie_parse_cipher_suite(const uint8_t *data,
|
|
enum ie_rsn_cipher_suite *out)
|
|
{
|
|
/*
|
|
* Compare the OUI to the ones we know. OUI Format is found in
|
|
* Figure 8-187 of 802.11
|
|
*/
|
|
if (!memcmp(data, ieee_oui, 3)) {
|
|
/* Suite type from Table 8-99 */
|
|
switch (data[3]) {
|
|
case 0:
|
|
*out = IE_RSN_CIPHER_SUITE_USE_GROUP_CIPHER;
|
|
return true;
|
|
case 1:
|
|
*out = IE_RSN_CIPHER_SUITE_WEP40;
|
|
return true;
|
|
case 2:
|
|
*out = IE_RSN_CIPHER_SUITE_TKIP;
|
|
return true;
|
|
case 4:
|
|
*out = IE_RSN_CIPHER_SUITE_CCMP;
|
|
return true;
|
|
case 5:
|
|
*out = IE_RSN_CIPHER_SUITE_WEP104;
|
|
return true;
|
|
case 6:
|
|
*out = IE_RSN_CIPHER_SUITE_BIP_CMAC;
|
|
return true;
|
|
case 7:
|
|
*out = IE_RSN_CIPHER_SUITE_NO_GROUP_TRAFFIC;
|
|
return true;
|
|
case 8:
|
|
*out = IE_RSN_CIPHER_SUITE_GCMP;
|
|
return true;
|
|
case 9:
|
|
*out = IE_RSN_CIPHER_SUITE_GCMP_256;
|
|
return true;
|
|
case 10:
|
|
*out = IE_RSN_CIPHER_SUITE_CCMP_256;
|
|
return true;
|
|
case 11:
|
|
*out = IE_RSN_CIPHER_SUITE_BIP_GMAC;
|
|
return true;
|
|
case 12:
|
|
*out = IE_RSN_CIPHER_SUITE_BIP_GMAC_256;
|
|
return true;
|
|
case 13:
|
|
*out = IE_RSN_CIPHER_SUITE_BIP_CMAC_256;
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/* 802.11, Section 8.4.2.27.2 */
|
|
static int ie_parse_rsn_akm_suite(const uint8_t *data,
|
|
enum ie_rsn_akm_suite *out)
|
|
{
|
|
/*
|
|
* Compare the OUI to the ones we know. OUI Format is found in
|
|
* Figure 8-187 of 802.11
|
|
*/
|
|
if (!memcmp(data, ieee_oui, 3)) {
|
|
/* Suite type from Table 8-101 */
|
|
switch (data[3]) {
|
|
case 0:
|
|
return -EINVAL;
|
|
case 1:
|
|
*out = IE_RSN_AKM_SUITE_8021X;
|
|
return 0;
|
|
case 2:
|
|
*out = IE_RSN_AKM_SUITE_PSK;
|
|
return 0;
|
|
case 3:
|
|
*out = IE_RSN_AKM_SUITE_FT_OVER_8021X;
|
|
return 0;
|
|
case 4:
|
|
*out = IE_RSN_AKM_SUITE_FT_USING_PSK;
|
|
return 0;
|
|
case 5:
|
|
*out = IE_RSN_AKM_SUITE_8021X_SHA256;
|
|
return 0;
|
|
case 6:
|
|
*out = IE_RSN_AKM_SUITE_PSK_SHA256;
|
|
return 0;
|
|
case 7:
|
|
*out = IE_RSN_AKM_SUITE_TDLS;
|
|
return 0;
|
|
case 8:
|
|
*out = IE_RSN_AKM_SUITE_SAE_SHA256;
|
|
return 0;
|
|
case 9:
|
|
*out = IE_RSN_AKM_SUITE_FT_OVER_SAE_SHA256;
|
|
return 0;
|
|
case 10:
|
|
*out = IE_RSN_AKM_SUITE_AP_PEER_KEY_SHA256;
|
|
return 0;
|
|
case 11:
|
|
*out = IE_RSN_AKM_SUITE_8021X_SUITE_B_SHA256;
|
|
return 0;
|
|
case 12:
|
|
*out = IE_RSN_AKM_SUITE_8021X_SUITE_B_SHA384;
|
|
return 0;
|
|
case 13:
|
|
*out = IE_RSN_AKM_SUITE_FT_OVER_8021X_SHA384;
|
|
return 0;
|
|
case 14:
|
|
*out = IE_RSN_AKM_SUITE_FILS_SHA256;
|
|
return 0;
|
|
case 15:
|
|
*out = IE_RSN_AKM_SUITE_FILS_SHA384;
|
|
return 0;
|
|
case 16:
|
|
*out = IE_RSN_AKM_SUITE_FT_OVER_FILS_SHA256;
|
|
return 0;
|
|
case 17:
|
|
*out = IE_RSN_AKM_SUITE_FT_OVER_FILS_SHA384;
|
|
return 0;
|
|
case 18:
|
|
*out = IE_RSN_AKM_SUITE_OWE;
|
|
return 0;
|
|
default:
|
|
return -ENOENT;
|
|
}
|
|
}
|
|
|
|
return -ENOENT;
|
|
}
|
|
|
|
static int ie_parse_osen_akm_suite(const uint8_t *data,
|
|
enum ie_rsn_akm_suite *out)
|
|
{
|
|
if (memcmp(data, wifi_alliance_oui, 3))
|
|
return -ENOENT;
|
|
|
|
if (data[3] != 1)
|
|
return -ENOENT;
|
|
|
|
*out = IE_RSN_AKM_SUITE_OSEN;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static bool ie_parse_group_cipher(const uint8_t *data,
|
|
enum ie_rsn_cipher_suite *out)
|
|
{
|
|
enum ie_rsn_cipher_suite tmp;
|
|
|
|
bool r = ie_parse_cipher_suite(data, &tmp);
|
|
|
|
if (!r)
|
|
return r;
|
|
|
|
switch (tmp) {
|
|
case IE_RSN_CIPHER_SUITE_CCMP:
|
|
case IE_RSN_CIPHER_SUITE_TKIP:
|
|
case IE_RSN_CIPHER_SUITE_WEP104:
|
|
case IE_RSN_CIPHER_SUITE_WEP40:
|
|
case IE_RSN_CIPHER_SUITE_NO_GROUP_TRAFFIC:
|
|
case IE_RSN_CIPHER_SUITE_GCMP:
|
|
case IE_RSN_CIPHER_SUITE_GCMP_256:
|
|
case IE_RSN_CIPHER_SUITE_CCMP_256:
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
*out = tmp;
|
|
return true;
|
|
}
|
|
|
|
static int ie_parse_pairwise_cipher(const uint8_t *data,
|
|
enum ie_rsn_cipher_suite *out)
|
|
{
|
|
enum ie_rsn_cipher_suite tmp;
|
|
bool r = ie_parse_cipher_suite(data, &tmp);
|
|
|
|
if (!r)
|
|
return -ENOENT;
|
|
|
|
switch (tmp) {
|
|
case IE_RSN_CIPHER_SUITE_CCMP:
|
|
case IE_RSN_CIPHER_SUITE_TKIP:
|
|
case IE_RSN_CIPHER_SUITE_WEP104:
|
|
case IE_RSN_CIPHER_SUITE_WEP40:
|
|
case IE_RSN_CIPHER_SUITE_USE_GROUP_CIPHER:
|
|
case IE_RSN_CIPHER_SUITE_GCMP:
|
|
case IE_RSN_CIPHER_SUITE_GCMP_256:
|
|
case IE_RSN_CIPHER_SUITE_CCMP_256:
|
|
break;
|
|
default:
|
|
return -ERANGE;
|
|
}
|
|
|
|
*out = tmp;
|
|
return 0;
|
|
}
|
|
|
|
static bool ie_parse_group_management_cipher(const uint8_t *data,
|
|
enum ie_rsn_cipher_suite *out)
|
|
{
|
|
enum ie_rsn_cipher_suite tmp;
|
|
|
|
bool r = ie_parse_cipher_suite(data, &tmp);
|
|
|
|
if (!r)
|
|
return r;
|
|
|
|
switch (tmp) {
|
|
case IE_RSN_CIPHER_SUITE_BIP_CMAC:
|
|
case IE_RSN_CIPHER_SUITE_NO_GROUP_TRAFFIC:
|
|
case IE_RSN_CIPHER_SUITE_BIP_GMAC:
|
|
case IE_RSN_CIPHER_SUITE_BIP_GMAC_256:
|
|
case IE_RSN_CIPHER_SUITE_BIP_CMAC_256:
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
*out = tmp;
|
|
return true;
|
|
}
|
|
|
|
#define RSNE_ADVANCE(data, len, step) \
|
|
data += step; \
|
|
len -= step; \
|
|
\
|
|
if (len == 0) \
|
|
goto done \
|
|
|
|
static int parse_ciphers(const uint8_t *data, size_t len,
|
|
int (*akm_parse)(const uint8_t *data,
|
|
enum ie_rsn_akm_suite *out),
|
|
struct ie_rsn_info *out_info)
|
|
{
|
|
uint16_t count;
|
|
uint16_t i;
|
|
|
|
/* Parse Group Cipher Suite field */
|
|
if (len < 4)
|
|
return -EBADMSG;
|
|
|
|
if (!ie_parse_group_cipher(data, &out_info->group_cipher))
|
|
return -ERANGE;
|
|
|
|
RSNE_ADVANCE(data, len, 4);
|
|
|
|
/* Parse Pairwise Cipher Suite Count field */
|
|
if (len < 2)
|
|
return -EBADMSG;
|
|
|
|
count = l_get_le16(data);
|
|
|
|
/*
|
|
* The spec doesn't seem to explicitly say what to do in this case,
|
|
* so we assume this situation is invalid.
|
|
*/
|
|
if (count == 0)
|
|
return -EINVAL;
|
|
|
|
data += 2;
|
|
len -= 2;
|
|
|
|
if (len < 4 * count)
|
|
return -EBADMSG;
|
|
|
|
/* Parse Pairwise Cipher Suite List field */
|
|
for (i = 0, out_info->pairwise_ciphers = 0; i < count; i++) {
|
|
enum ie_rsn_cipher_suite suite;
|
|
int r = ie_parse_pairwise_cipher(data + i * 4, &suite);
|
|
|
|
if (r == -ENOENT) /* Skip unknown */
|
|
continue;
|
|
else if (r < 0)
|
|
return r;
|
|
|
|
out_info->pairwise_ciphers |= suite;
|
|
}
|
|
|
|
RSNE_ADVANCE(data, len, count * 4);
|
|
|
|
/* Parse AKM Suite Count field */
|
|
if (len < 2)
|
|
return -EBADMSG;
|
|
|
|
count = l_get_le16(data);
|
|
if (count == 0)
|
|
return -EINVAL;
|
|
|
|
data += 2;
|
|
len -= 2;
|
|
|
|
if (len < 4 * count)
|
|
return -EBADMSG;
|
|
|
|
/* Parse AKM Suite List field */
|
|
for (i = 0, out_info->akm_suites = 0; i < count; i++) {
|
|
enum ie_rsn_akm_suite suite;
|
|
int ret;
|
|
|
|
ret = akm_parse(data + i * 4, &suite);
|
|
switch (ret) {
|
|
case 0:
|
|
out_info->akm_suites |= suite;
|
|
break;
|
|
case -ENOENT:
|
|
/* Skip unknown or vendor specific AKMs */
|
|
break;
|
|
default:
|
|
return -EBADMSG;
|
|
}
|
|
}
|
|
|
|
RSNE_ADVANCE(data, len, count * 4);
|
|
|
|
if (len < 2)
|
|
return -EBADMSG;
|
|
|
|
out_info->preauthentication = test_bit(data, 0);
|
|
out_info->no_pairwise = test_bit(data, 1);
|
|
out_info->ptksa_replay_counter = bit_field(data[0], 2, 2);
|
|
out_info->gtksa_replay_counter = bit_field(data[0], 4, 2);
|
|
out_info->mfpr = test_bit(data, 6);
|
|
out_info->mfpc = test_bit(data, 7);
|
|
out_info->peerkey_enabled = test_bit(data + 1, 1);
|
|
out_info->spp_a_msdu_capable = test_bit(data + 1, 2);
|
|
out_info->spp_a_msdu_required = test_bit(data + 1, 3);
|
|
out_info->pbac = test_bit(data + 1, 4);
|
|
out_info->extended_key_id = test_bit(data + 1, 5);
|
|
out_info->ocvc = test_bit(data + 1, 6);
|
|
|
|
/*
|
|
* BIP-default group management cipher suite in an RSNA with
|
|
* management frame protection enabled
|
|
*/
|
|
if (out_info->mfpc)
|
|
out_info->group_management_cipher =
|
|
IE_RSN_CIPHER_SUITE_BIP_CMAC;
|
|
|
|
RSNE_ADVANCE(data, len, 2);
|
|
|
|
/* Parse PMKID Count field */
|
|
if (len < 2)
|
|
return -EBADMSG;
|
|
|
|
out_info->num_pmkids = l_get_le16(data);
|
|
RSNE_ADVANCE(data, len, 2);
|
|
|
|
if (out_info->num_pmkids > 0) {
|
|
if (len < 16 * out_info->num_pmkids)
|
|
return -EBADMSG;
|
|
|
|
/*
|
|
* Parse PMKID List field.
|
|
*
|
|
* We simply assign the pointer to the PMKIDs to the structure.
|
|
* The PMKIDs are fixed size, 16 bytes each.
|
|
*/
|
|
out_info->pmkids = data;
|
|
RSNE_ADVANCE(data, len, out_info->num_pmkids * 16);
|
|
}
|
|
|
|
/* Parse Group Management Cipher Suite field */
|
|
if (len < 4)
|
|
return -EBADMSG;
|
|
|
|
if (!ie_parse_group_management_cipher(data,
|
|
&out_info->group_management_cipher))
|
|
return -ERANGE;
|
|
|
|
RSNE_ADVANCE(data, len, 4);
|
|
|
|
return -EBADMSG;
|
|
|
|
done:
|
|
return 0;
|
|
}
|
|
|
|
int ie_parse_rsne(struct ie_tlv_iter *iter, struct ie_rsn_info *out_info)
|
|
{
|
|
const uint8_t *data = iter->data;
|
|
size_t len = iter->len;
|
|
uint16_t version;
|
|
struct ie_rsn_info info;
|
|
|
|
memset(&info, 0, sizeof(info));
|
|
info.group_cipher = IE_RSN_CIPHER_SUITE_CCMP;
|
|
info.pairwise_ciphers = IE_RSN_CIPHER_SUITE_CCMP;
|
|
info.akm_suites = IE_RSN_AKM_SUITE_8021X;
|
|
|
|
/* Parse Version field */
|
|
if (len < 2)
|
|
return -EMSGSIZE;
|
|
|
|
version = l_get_le16(data);
|
|
if (version != 0x01)
|
|
return -EBADMSG;
|
|
|
|
RSNE_ADVANCE(data, len, 2);
|
|
|
|
if (parse_ciphers(data, len, ie_parse_rsn_akm_suite, &info) < 0)
|
|
return -EBADMSG;
|
|
|
|
done:
|
|
if (out_info)
|
|
memcpy(out_info, &info, sizeof(info));
|
|
|
|
return 0;
|
|
}
|
|
|
|
int ie_parse_rsne_from_data(const uint8_t *data, size_t len,
|
|
struct ie_rsn_info *info)
|
|
{
|
|
struct ie_tlv_iter iter;
|
|
|
|
ie_tlv_iter_init(&iter, data, len);
|
|
|
|
if (!ie_tlv_iter_next(&iter))
|
|
return -EMSGSIZE;
|
|
|
|
if (ie_tlv_iter_get_tag(&iter) != IE_TYPE_RSN)
|
|
return -EPROTOTYPE;
|
|
|
|
return ie_parse_rsne(&iter, info);
|
|
}
|
|
|
|
int ie_parse_osen(struct ie_tlv_iter *iter, struct ie_rsn_info *out_info)
|
|
{
|
|
const uint8_t *data = iter->data;
|
|
size_t len = iter->len;
|
|
struct ie_rsn_info info;
|
|
|
|
if (ie_tlv_iter_get_tag(iter) != IE_TYPE_VENDOR_SPECIFIC)
|
|
return -EPROTOTYPE;
|
|
|
|
if (!is_ie_wfa_ie(iter->data, iter->len, IE_WFA_OI_OSEN))
|
|
return -EPROTOTYPE;
|
|
|
|
memset(&info, 0, sizeof(info));
|
|
info.group_cipher = IE_RSN_CIPHER_SUITE_NO_GROUP_TRAFFIC;
|
|
info.pairwise_ciphers = IE_RSN_CIPHER_SUITE_CCMP;
|
|
info.akm_suites = IE_RSN_AKM_SUITE_8021X;
|
|
|
|
RSNE_ADVANCE(data, len, 4);
|
|
|
|
if (parse_ciphers(data, len, ie_parse_osen_akm_suite, &info) < 0)
|
|
return -EBADMSG;
|
|
|
|
done:
|
|
if (out_info)
|
|
memcpy(out_info, &info, sizeof(info));
|
|
|
|
return 0;
|
|
}
|
|
|
|
int ie_parse_osen_from_data(const uint8_t *data, size_t len,
|
|
struct ie_rsn_info *info)
|
|
{
|
|
struct ie_tlv_iter iter;
|
|
|
|
ie_tlv_iter_init(&iter, data, len);
|
|
|
|
if (!ie_tlv_iter_next(&iter))
|
|
return -EMSGSIZE;
|
|
|
|
return ie_parse_osen(&iter, info);
|
|
}
|
|
|
|
/*
|
|
* 802.11, Section 8.4.2.27.2
|
|
* 802.11i, Section 7.3.2.25.1 and WPA_80211_v3_1 Section 2.1
|
|
*/
|
|
static bool ie_build_cipher_suite(uint8_t *data, const uint8_t *oui,
|
|
const enum ie_rsn_cipher_suite suite)
|
|
{
|
|
uint8_t selector;
|
|
|
|
switch (suite) {
|
|
case IE_RSN_CIPHER_SUITE_USE_GROUP_CIPHER:
|
|
selector = 0;
|
|
goto done;
|
|
case IE_RSN_CIPHER_SUITE_WEP40:
|
|
selector = 1;
|
|
goto done;
|
|
case IE_RSN_CIPHER_SUITE_TKIP:
|
|
selector = 2;
|
|
goto done;
|
|
case IE_RSN_CIPHER_SUITE_CCMP:
|
|
selector = 4;
|
|
goto done;
|
|
case IE_RSN_CIPHER_SUITE_WEP104:
|
|
selector = 5;
|
|
goto done;
|
|
case IE_RSN_CIPHER_SUITE_BIP_CMAC:
|
|
selector = 6;
|
|
goto done;
|
|
case IE_RSN_CIPHER_SUITE_NO_GROUP_TRAFFIC:
|
|
selector = 7;
|
|
goto done;
|
|
case IE_RSN_CIPHER_SUITE_GCMP:
|
|
selector = 8;
|
|
goto done;
|
|
case IE_RSN_CIPHER_SUITE_GCMP_256:
|
|
selector = 9;
|
|
goto done;
|
|
case IE_RSN_CIPHER_SUITE_CCMP_256:
|
|
selector = 10;
|
|
goto done;
|
|
case IE_RSN_CIPHER_SUITE_BIP_GMAC:
|
|
selector = 11;
|
|
goto done;
|
|
case IE_RSN_CIPHER_SUITE_BIP_GMAC_256:
|
|
selector = 12;
|
|
goto done;
|
|
case IE_RSN_CIPHER_SUITE_BIP_CMAC_256:
|
|
selector = 13;
|
|
goto done;
|
|
}
|
|
|
|
return false;
|
|
done:
|
|
memcpy(data, oui, 3);
|
|
data[3] = selector;
|
|
return true;
|
|
}
|
|
|
|
#define RETURN_AKM(data, oui, id) \
|
|
do { \
|
|
memcpy((data), (oui), 3); \
|
|
(data)[3] = (id); \
|
|
return true; \
|
|
} while (0)
|
|
|
|
/* 802.11-2016, Section 9.4.2.25.3 */
|
|
static bool ie_build_rsn_akm_suite(uint8_t *data, enum ie_rsn_akm_suite suite)
|
|
{
|
|
switch (suite) {
|
|
case IE_RSN_AKM_SUITE_8021X:
|
|
RETURN_AKM(data, ieee_oui, 1);
|
|
case IE_RSN_AKM_SUITE_PSK:
|
|
RETURN_AKM(data, ieee_oui, 2);
|
|
case IE_RSN_AKM_SUITE_FT_OVER_8021X:
|
|
RETURN_AKM(data, ieee_oui, 3);
|
|
case IE_RSN_AKM_SUITE_FT_USING_PSK:
|
|
RETURN_AKM(data, ieee_oui, 4);
|
|
case IE_RSN_AKM_SUITE_8021X_SHA256:
|
|
RETURN_AKM(data, ieee_oui, 5);
|
|
case IE_RSN_AKM_SUITE_PSK_SHA256:
|
|
RETURN_AKM(data, ieee_oui, 6);
|
|
case IE_RSN_AKM_SUITE_TDLS:
|
|
RETURN_AKM(data, ieee_oui, 7);
|
|
case IE_RSN_AKM_SUITE_SAE_SHA256:
|
|
RETURN_AKM(data, ieee_oui, 8);
|
|
case IE_RSN_AKM_SUITE_FT_OVER_SAE_SHA256:
|
|
RETURN_AKM(data, ieee_oui, 9);
|
|
case IE_RSN_AKM_SUITE_AP_PEER_KEY_SHA256:
|
|
RETURN_AKM(data, ieee_oui, 10);
|
|
case IE_RSN_AKM_SUITE_8021X_SUITE_B_SHA256:
|
|
RETURN_AKM(data, ieee_oui, 11);
|
|
case IE_RSN_AKM_SUITE_8021X_SUITE_B_SHA384:
|
|
RETURN_AKM(data, ieee_oui, 12);
|
|
case IE_RSN_AKM_SUITE_FT_OVER_8021X_SHA384:
|
|
RETURN_AKM(data, ieee_oui, 13);
|
|
case IE_RSN_AKM_SUITE_FILS_SHA256:
|
|
RETURN_AKM(data, ieee_oui, 14);
|
|
case IE_RSN_AKM_SUITE_FILS_SHA384:
|
|
RETURN_AKM(data, ieee_oui, 15);
|
|
case IE_RSN_AKM_SUITE_FT_OVER_FILS_SHA256:
|
|
RETURN_AKM(data, ieee_oui, 16);
|
|
case IE_RSN_AKM_SUITE_FT_OVER_FILS_SHA384:
|
|
RETURN_AKM(data, ieee_oui, 17);
|
|
case IE_RSN_AKM_SUITE_OWE:
|
|
RETURN_AKM(data, ieee_oui, 18);
|
|
case IE_RSN_AKM_SUITE_OSEN:
|
|
RETURN_AKM(data, wifi_alliance_oui, 1);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/* 802.11i, Section 7.3.2.25.2 and WPA_80211_v3_1 Section 2.1 */
|
|
static bool ie_build_wpa_akm_suite(uint8_t *data, enum ie_rsn_akm_suite suite)
|
|
{
|
|
switch (suite) {
|
|
case IE_RSN_AKM_SUITE_8021X:
|
|
RETURN_AKM(data, microsoft_oui, 1);
|
|
case IE_RSN_AKM_SUITE_PSK:
|
|
RETURN_AKM(data, microsoft_oui, 2);
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static int build_ciphers_common(const struct ie_rsn_info *info, uint8_t *to,
|
|
uint8_t max_len, bool force_group_mgmt_cipher)
|
|
{
|
|
/* These are the only valid pairwise suites */
|
|
static enum ie_rsn_cipher_suite pairwise_suites[] = {
|
|
IE_RSN_CIPHER_SUITE_CCMP,
|
|
IE_RSN_CIPHER_SUITE_TKIP,
|
|
IE_RSN_CIPHER_SUITE_WEP104,
|
|
IE_RSN_CIPHER_SUITE_WEP40,
|
|
IE_RSN_CIPHER_SUITE_USE_GROUP_CIPHER,
|
|
IE_RSN_CIPHER_SUITE_GCMP,
|
|
IE_RSN_CIPHER_SUITE_GCMP_256,
|
|
IE_RSN_CIPHER_SUITE_CCMP_256,
|
|
};
|
|
unsigned int pos = 0;
|
|
unsigned int i;
|
|
uint8_t *countptr;
|
|
uint16_t count;
|
|
enum ie_rsn_akm_suite akm_suite;
|
|
|
|
/* Group Data Cipher Suite */
|
|
if (!ie_build_cipher_suite(to + pos, ieee_oui, info->group_cipher))
|
|
return -EINVAL;
|
|
|
|
pos += 4;
|
|
|
|
/* Save position for Pairwise Cipher Suite Count field */
|
|
countptr = to + pos;
|
|
pos += 2;
|
|
|
|
for (i = 0, count = 0; i < L_ARRAY_SIZE(pairwise_suites); i++) {
|
|
enum ie_rsn_cipher_suite suite = pairwise_suites[i];
|
|
|
|
if (!(info->pairwise_ciphers & suite))
|
|
continue;
|
|
|
|
if (pos + 4 > max_len)
|
|
return -EBADMSG;
|
|
|
|
if (!ie_build_cipher_suite(to + pos, ieee_oui, suite))
|
|
return -EINVAL;
|
|
|
|
pos += 4;
|
|
count += 1;
|
|
}
|
|
|
|
l_put_le16(count, countptr);
|
|
|
|
/* Save position for AKM Suite Count field */
|
|
countptr = to + pos;
|
|
pos += 2;
|
|
|
|
akm_suite = IE_RSN_AKM_SUITE_8021X;
|
|
count = 0;
|
|
|
|
for (count = 0, akm_suite = IE_RSN_AKM_SUITE_8021X;
|
|
akm_suite <= IE_RSN_AKM_SUITE_OSEN;
|
|
akm_suite <<= 1) {
|
|
if (!(info->akm_suites & akm_suite))
|
|
continue;
|
|
|
|
if (pos + 4 > max_len)
|
|
return -EBADMSG;
|
|
|
|
if (!ie_build_rsn_akm_suite(to + pos, akm_suite))
|
|
return -EINVAL;
|
|
|
|
pos += 4;
|
|
count += 1;
|
|
}
|
|
|
|
l_put_le16(count, countptr);
|
|
|
|
/* Bits 0 - 7 of RSNE Capabilities field */
|
|
to[pos] = 0;
|
|
|
|
if (info->preauthentication)
|
|
to[pos] |= 0x1;
|
|
|
|
if (info->no_pairwise)
|
|
to[pos] |= 0x2;
|
|
|
|
to[pos] |= info->ptksa_replay_counter << 2;
|
|
to[pos] |= info->gtksa_replay_counter << 4;
|
|
|
|
if (info->mfpr)
|
|
to[pos] |= 0x40;
|
|
|
|
if (info->mfpc)
|
|
to[pos] |= 0x80;
|
|
|
|
pos += 1;
|
|
|
|
/* Bits 8 - 15 of RSNE Capabilities field */
|
|
to[pos] = 0;
|
|
|
|
if (info->peerkey_enabled)
|
|
to[pos] |= 0x2;
|
|
|
|
if (info->spp_a_msdu_capable)
|
|
to[pos] |= 0x4;
|
|
|
|
if (info->spp_a_msdu_required)
|
|
to[pos] |= 0x8;
|
|
|
|
if (info->pbac)
|
|
to[pos] |= 0x10;
|
|
|
|
if (info->extended_key_id)
|
|
to[pos] |= 0x20;
|
|
|
|
if (info->ocvc)
|
|
to[pos] |= 0x40;
|
|
|
|
pos += 1;
|
|
|
|
/* Short hand the generated RSNE if possible */
|
|
if (info->num_pmkids == 0 && !force_group_mgmt_cipher) {
|
|
/* No Group Management Cipher Suite */
|
|
if (to[pos - 2] == 0 && to[pos - 1] == 0)
|
|
/*
|
|
* The RSN Capabilities bytes are in theory optional,
|
|
* but some APs don't seem to like us not including
|
|
* them in the RSN element. Also wireshark has a
|
|
* bug and complains of a malformed element if these
|
|
* bytes are not included.
|
|
*/
|
|
goto done;
|
|
else if (!info->mfpc)
|
|
goto done;
|
|
else if (info->group_management_cipher ==
|
|
IE_RSN_CIPHER_SUITE_BIP_CMAC)
|
|
goto done;
|
|
}
|
|
|
|
/* PMKID Count */
|
|
l_put_le16(info->num_pmkids, to + pos);
|
|
pos += 2;
|
|
|
|
if (pos + info->num_pmkids * 16 > max_len)
|
|
return -EINVAL;
|
|
|
|
/* PMKID List */
|
|
if (info->num_pmkids) {
|
|
memcpy(to + pos, info->pmkids, 16 * info->num_pmkids);
|
|
pos += 16 * info->num_pmkids;
|
|
}
|
|
|
|
if (!force_group_mgmt_cipher && !info->mfpc)
|
|
goto done;
|
|
|
|
if (!force_group_mgmt_cipher && info->group_management_cipher ==
|
|
IE_RSN_CIPHER_SUITE_BIP_CMAC)
|
|
goto done;
|
|
|
|
/* Group Management Cipher Suite */
|
|
if (!ie_build_cipher_suite(to + pos, ieee_oui,
|
|
info->group_management_cipher))
|
|
return -EINVAL;
|
|
|
|
pos += 4;
|
|
|
|
done:
|
|
return pos;
|
|
}
|
|
|
|
/*
|
|
* Generate an RSNE IE based on the information found in info.
|
|
* The to array must be 256 bytes in size
|
|
*
|
|
* In theory it is possible to generate 257 byte IE RSNs (1 byte for IE Type,
|
|
* 1 byte for Length and 255 bytes of data) but we don't support this
|
|
* possibility.
|
|
*/
|
|
bool ie_build_rsne(const struct ie_rsn_info *info, uint8_t *to)
|
|
{
|
|
unsigned int pos;
|
|
int ret;
|
|
|
|
to[0] = IE_TYPE_RSN;
|
|
|
|
/* Version field, always 1 */
|
|
pos = 2;
|
|
l_put_le16(1, to + pos);
|
|
pos += 2;
|
|
|
|
ret = build_ciphers_common(info, to + 4, 252, false);
|
|
if (ret < 0)
|
|
return false;
|
|
|
|
pos += ret;
|
|
|
|
to[1] = pos - 2;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ie_rsne_is_wpa3_personal(const struct ie_rsn_info *info)
|
|
{
|
|
bool is_transition = info->akm_suites & IE_RSN_AKM_SUITE_PSK;
|
|
/*
|
|
* WPA3 Specification, Version 2
|
|
*
|
|
* Section 2.2 WPA3-Personal only Mode:
|
|
* 1. An AP shall enable at least AKM suite selector 00-0F-AC:8 in
|
|
* the BSS
|
|
* 3. An AP shall not enable AKM suite selector: 00-0F-AC:2, 00-0F-AC:6
|
|
* 5. an AP shall set MFPC to 1, MFPR to 1
|
|
*
|
|
* Section 2.3 WPA3-Personal transition Mode:
|
|
* 1. an AP shall enable at least AKM suite selectors 00-0F-AC:2 and
|
|
* 00-0F-AC:8 in the BSS
|
|
* 3. an AP should enable AKM suite selector: 00-0F-AC:6
|
|
* 5. an AP shall set MFPC to 1, MFPR to 0
|
|
*/
|
|
if (!(info->akm_suites & IE_RSN_AKM_SUITE_SAE_SHA256))
|
|
return false;
|
|
|
|
if (!info->mfpc)
|
|
return false;
|
|
|
|
return is_transition || info->mfpr;
|
|
}
|
|
|
|
bool ie_build_osen(const struct ie_rsn_info *info, uint8_t *to)
|
|
{
|
|
unsigned int pos;
|
|
int ret;
|
|
|
|
to[0] = IE_TYPE_VENDOR_SPECIFIC;
|
|
pos = 2;
|
|
memcpy(to + pos, wifi_alliance_oui, 3);
|
|
pos += 3;
|
|
to[pos++] = 0x12;
|
|
|
|
ret = build_ciphers_common(info, to + 6, 250, true);
|
|
if (ret < 0)
|
|
return false;
|
|
|
|
pos += ret;
|
|
|
|
to[1] = pos - 2;
|
|
|
|
return true;
|
|
}
|
|
|
|
/* 802.11i-2004, Section 7.3.2.25.1 and WPA_80211_v3_1 Section 2.1 */
|
|
static bool ie_parse_wpa_cipher_suite(const uint8_t *data,
|
|
enum ie_rsn_cipher_suite *out)
|
|
{
|
|
/*
|
|
* Compare the OUI to the ones we know. OUI Format is found in
|
|
* Figure 8-187 of 802.11
|
|
*/
|
|
if (!memcmp(data, microsoft_oui, 3)) {
|
|
/* Suite type from 802.11i-2004, Table 20da */
|
|
switch (data[3]) {
|
|
case 0:
|
|
*out = IE_RSN_CIPHER_SUITE_USE_GROUP_CIPHER;
|
|
return true;
|
|
case 1:
|
|
*out = IE_RSN_CIPHER_SUITE_WEP40;
|
|
return true;
|
|
case 2:
|
|
*out = IE_RSN_CIPHER_SUITE_TKIP;
|
|
return true;
|
|
case 4:
|
|
*out = IE_RSN_CIPHER_SUITE_CCMP;
|
|
return true;
|
|
case 5:
|
|
*out = IE_RSN_CIPHER_SUITE_WEP104;
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/* 802.11i-2004, Section 7.3.2.25.2 and WPA_80211_v3_1 Section 2.1 */
|
|
static bool ie_parse_wpa_akm_suite(const uint8_t *data,
|
|
enum ie_rsn_akm_suite *out)
|
|
{
|
|
/*
|
|
* Compare the OUI to the ones we know. OUI Format is found in
|
|
* Figure 8-187 of 802.11
|
|
*/
|
|
if (!memcmp(data, microsoft_oui, 3)) {
|
|
/* Suite type from 802.11i-2004, Table 20dc */
|
|
switch (data[3]) {
|
|
case 1:
|
|
*out = IE_RSN_AKM_SUITE_8021X;
|
|
return true;
|
|
case 2:
|
|
*out = IE_RSN_AKM_SUITE_PSK;
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static bool ie_parse_wpa_group_cipher(const uint8_t *data,
|
|
enum ie_rsn_cipher_suite *out)
|
|
{
|
|
enum ie_rsn_cipher_suite tmp;
|
|
|
|
bool r = ie_parse_wpa_cipher_suite(data, &tmp);
|
|
|
|
if (!r)
|
|
return r;
|
|
|
|
switch (tmp) {
|
|
case IE_RSN_CIPHER_SUITE_CCMP:
|
|
case IE_RSN_CIPHER_SUITE_TKIP:
|
|
case IE_RSN_CIPHER_SUITE_WEP104:
|
|
case IE_RSN_CIPHER_SUITE_WEP40:
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
*out = tmp;
|
|
return true;
|
|
}
|
|
|
|
static bool ie_parse_wpa_pairwise_cipher(const uint8_t *data,
|
|
enum ie_rsn_cipher_suite *out)
|
|
{
|
|
enum ie_rsn_cipher_suite tmp;
|
|
|
|
bool r = ie_parse_wpa_cipher_suite(data, &tmp);
|
|
|
|
if (!r)
|
|
return r;
|
|
|
|
switch (tmp) {
|
|
case IE_RSN_CIPHER_SUITE_CCMP:
|
|
case IE_RSN_CIPHER_SUITE_TKIP:
|
|
case IE_RSN_CIPHER_SUITE_WEP104:
|
|
case IE_RSN_CIPHER_SUITE_WEP40:
|
|
/* TODO : not sure about GROUP_CIPHER */
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
*out = tmp;
|
|
return true;
|
|
}
|
|
|
|
bool is_ie_wfa_ie(const uint8_t *data, uint8_t len, uint8_t oi_type)
|
|
{
|
|
if (!data)
|
|
return false;
|
|
|
|
if (oi_type == IE_WFA_OI_OSEN && len < 22)
|
|
return false;
|
|
else if (oi_type == IE_WFA_OI_HS20_INDICATION && len != 5 && len != 7)
|
|
return false;
|
|
else if (oi_type == IE_WFA_OI_OWE_TRANSITION && len < 12)
|
|
return false;
|
|
else if (len < 4) /* OI not handled, but at least check length */
|
|
return false;
|
|
|
|
if (!memcmp(data, wifi_alliance_oui, 3) && data[3] == oi_type)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
bool is_ie_wpa_ie(const uint8_t *data, uint8_t len)
|
|
{
|
|
if (!data || len < 6)
|
|
return false;
|
|
|
|
if ((!memcmp(data, microsoft_oui, 3) && data[3] == 1 &&
|
|
l_get_le16(data + 4) == 1))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* List of vendor OUIs (prefixed with a length byte) which require forcing
|
|
* the default SAE group.
|
|
*/
|
|
static const uint8_t use_default_sae_group_ouis[][5] = {
|
|
{ 0x04, 0xf4, 0xf5, 0xe8, 0x05 },
|
|
};
|
|
|
|
bool is_ie_default_sae_group_oui(const uint8_t *data, uint16_t len)
|
|
{
|
|
unsigned int i;
|
|
const uint8_t *oui;
|
|
|
|
for (i = 0; i < L_ARRAY_SIZE(use_default_sae_group_ouis); i++) {
|
|
oui = use_default_sae_group_ouis[i];
|
|
|
|
if (len < oui[0])
|
|
continue;
|
|
|
|
if (!memcmp(oui + 1, data, oui[0]))
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
int ie_parse_wpa(struct ie_tlv_iter *iter, struct ie_rsn_info *out_info)
|
|
{
|
|
const uint8_t *data = iter->data;
|
|
size_t len = iter->len;
|
|
struct ie_rsn_info info;
|
|
uint16_t count;
|
|
uint16_t i;
|
|
|
|
if (!is_ie_wpa_ie(iter->data, iter->len))
|
|
return -EINVAL;
|
|
|
|
memset(&info, 0, sizeof(info));
|
|
info.group_cipher = IE_RSN_CIPHER_SUITE_TKIP;
|
|
info.pairwise_ciphers = IE_RSN_CIPHER_SUITE_TKIP;
|
|
info.akm_suites = IE_RSN_AKM_SUITE_PSK;
|
|
|
|
RSNE_ADVANCE(data, len, 6);
|
|
|
|
/* Parse Group Cipher Suite field */
|
|
if (len < 4)
|
|
return -EBADMSG;
|
|
|
|
if (!ie_parse_wpa_group_cipher(data, &info.group_cipher))
|
|
return -ERANGE;
|
|
|
|
RSNE_ADVANCE(data, len, 4);
|
|
|
|
/* Parse Pairwise Cipher Suite Count field */
|
|
if (len < 2)
|
|
return -EBADMSG;
|
|
|
|
count = l_get_le16(data);
|
|
|
|
/*
|
|
* The spec doesn't seem to explicitly say what to do in this case,
|
|
* so we assume this situation is invalid.
|
|
*/
|
|
if (count == 0)
|
|
return -EINVAL;
|
|
|
|
data += 2;
|
|
len -= 2;
|
|
|
|
if (len < 4 * count)
|
|
return -EBADMSG;
|
|
|
|
/* Parse Pairwise Cipher Suite List field */
|
|
for (i = 0, info.pairwise_ciphers = 0; i < count; i++) {
|
|
enum ie_rsn_cipher_suite suite;
|
|
|
|
if (!ie_parse_wpa_pairwise_cipher(data + i * 4, &suite))
|
|
return -ERANGE;
|
|
|
|
info.pairwise_ciphers |= suite;
|
|
}
|
|
|
|
RSNE_ADVANCE(data, len, count * 4);
|
|
|
|
/* Parse AKM Suite Count field */
|
|
if (len < 2)
|
|
return -EBADMSG;
|
|
|
|
count = l_get_le16(data);
|
|
if (count == 0)
|
|
return -EINVAL;
|
|
|
|
data += 2;
|
|
len -= 2;
|
|
|
|
if (len < 4 * count)
|
|
return -EBADMSG;
|
|
|
|
/* Parse AKM Suite List field */
|
|
for (i = 0, info.akm_suites = 0; i < count; i++) {
|
|
enum ie_rsn_akm_suite suite;
|
|
|
|
if (!ie_parse_wpa_akm_suite(data + i * 4, &suite))
|
|
return -ERANGE;
|
|
|
|
info.akm_suites |= suite;
|
|
}
|
|
|
|
RSNE_ADVANCE(data, len, count * 4);
|
|
|
|
if (len < 2)
|
|
return -EBADMSG;
|
|
|
|
out_info->preauthentication = test_bit(data, 0);
|
|
out_info->no_pairwise = test_bit(data, 1);
|
|
out_info->ptksa_replay_counter = bit_field(data[0], 2, 2);
|
|
out_info->gtksa_replay_counter = bit_field(data[0], 4, 2);
|
|
|
|
RSNE_ADVANCE(data, len, 2);
|
|
|
|
l_warn("Received WPA element with extra trailing bytes -"
|
|
" which will be ignored");
|
|
return 0;
|
|
|
|
done:
|
|
/*
|
|
* 802.11i, Section 7.3.2.25.1
|
|
* Use of CCMP as the group cipher suite with TKIP as the
|
|
* pairwise cipher suite shall not be supported.
|
|
*/
|
|
if (info.group_cipher & IE_RSN_CIPHER_SUITE_CCMP &&
|
|
info.pairwise_ciphers & IE_RSN_CIPHER_SUITE_TKIP)
|
|
return -EBADMSG;
|
|
|
|
if (out_info)
|
|
memcpy(out_info, &info, sizeof(info));
|
|
|
|
return 0;
|
|
}
|
|
|
|
int ie_parse_wpa_from_data(const uint8_t *data, size_t len,
|
|
struct ie_rsn_info *info)
|
|
{
|
|
struct ie_tlv_iter iter;
|
|
|
|
ie_tlv_iter_init(&iter, data, len);
|
|
|
|
if (!ie_tlv_iter_next(&iter))
|
|
return -EMSGSIZE;
|
|
|
|
if (ie_tlv_iter_get_tag(&iter) != IE_TYPE_VENDOR_SPECIFIC)
|
|
return -EPROTOTYPE;
|
|
|
|
return ie_parse_wpa(&iter, info);
|
|
}
|
|
|
|
/*
|
|
* Generate an WPA IE based on the information found in info.
|
|
* The to array must be minimum of 19 bytes in size
|
|
*/
|
|
bool ie_build_wpa(const struct ie_rsn_info *info, uint8_t *to)
|
|
{
|
|
/* These are the only valid pairwise suites */
|
|
static enum ie_rsn_cipher_suite pairwise_suites[] = {
|
|
IE_RSN_CIPHER_SUITE_CCMP,
|
|
IE_RSN_CIPHER_SUITE_TKIP,
|
|
IE_RSN_CIPHER_SUITE_WEP104,
|
|
IE_RSN_CIPHER_SUITE_WEP40,
|
|
/* TODO: not sure about USE_GROUP_CIPHER,*/
|
|
};
|
|
/* These are the only valid AKM suites */
|
|
static enum ie_rsn_akm_suite akm_suites[] = {
|
|
IE_RSN_AKM_SUITE_8021X,
|
|
IE_RSN_AKM_SUITE_PSK,
|
|
};
|
|
unsigned int pos;
|
|
unsigned int i;
|
|
uint8_t *countptr;
|
|
uint16_t count;
|
|
|
|
/*
|
|
* 802.11i, Section 7.3.2.25.1
|
|
* Use of CCMP as the group cipher suite with TKIP as the
|
|
* pairwise cipher suite shall not be supported.
|
|
*/
|
|
if (info->group_cipher == IE_RSN_CIPHER_SUITE_CCMP &&
|
|
info->pairwise_ciphers & IE_RSN_CIPHER_SUITE_TKIP)
|
|
return false;
|
|
|
|
to[0] = IE_TYPE_VENDOR_SPECIFIC;
|
|
|
|
/* Vendor OUI and Type */
|
|
pos = 2;
|
|
memcpy(to + pos, microsoft_oui, 3);
|
|
pos += 3;
|
|
to[pos] = 1; /* OUI type 1 means WPA element */
|
|
pos++;
|
|
|
|
/* Version field, always 1 */
|
|
l_put_le16(1, to + pos);
|
|
pos += 2;
|
|
|
|
/* Group Data Cipher Suite */
|
|
if (!ie_build_cipher_suite(to + pos, microsoft_oui,
|
|
info->group_cipher))
|
|
return false;
|
|
|
|
pos += 4;
|
|
|
|
/* Save position for Pairwise Cipher Suite Count field */
|
|
countptr = to + pos;
|
|
pos += 2;
|
|
|
|
for (i = 0, count = 0; i < L_ARRAY_SIZE(pairwise_suites); i++) {
|
|
enum ie_rsn_cipher_suite suite = pairwise_suites[i];
|
|
|
|
if (!(info->pairwise_ciphers & suite))
|
|
continue;
|
|
|
|
if (!ie_build_cipher_suite(to + pos, microsoft_oui, suite))
|
|
return false;
|
|
|
|
pos += 4;
|
|
count += 1;
|
|
}
|
|
|
|
l_put_le16(count, countptr);
|
|
|
|
/* Save position for AKM Suite Count field */
|
|
countptr = to + pos;
|
|
pos += 2;
|
|
|
|
for (i = 0, count = 0; i < L_ARRAY_SIZE(akm_suites); i++) {
|
|
enum ie_rsn_akm_suite suite = akm_suites[i];
|
|
|
|
if (!(info->akm_suites & suite))
|
|
continue;
|
|
|
|
if (!ie_build_wpa_akm_suite(to + pos, suite))
|
|
return false;
|
|
|
|
pos += 4;
|
|
count += 1;
|
|
}
|
|
|
|
l_put_le16(count, countptr);
|
|
|
|
to[1] = pos - 2;
|
|
|
|
return true;
|
|
}
|
|
|
|
int ie_parse_bss_load(struct ie_tlv_iter *iter, uint16_t *out_sta_count,
|
|
uint8_t *out_channel_utilization,
|
|
uint16_t *out_admission_capacity)
|
|
{
|
|
const uint8_t *data;
|
|
|
|
if (ie_tlv_iter_get_length(iter) != 5)
|
|
return -EINVAL;
|
|
|
|
data = ie_tlv_iter_get_data(iter);
|
|
|
|
if (out_sta_count)
|
|
*out_sta_count = data[0] | data[1] << 8;
|
|
|
|
if (out_channel_utilization)
|
|
*out_channel_utilization = data[2];
|
|
|
|
if (out_admission_capacity)
|
|
*out_admission_capacity = data[3] | data[4] << 8;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int ie_parse_bss_load_from_data(const uint8_t *data, uint8_t len,
|
|
uint16_t *out_sta_count,
|
|
uint8_t *out_channel_utilization,
|
|
uint16_t *out_admission_capacity)
|
|
{
|
|
struct ie_tlv_iter iter;
|
|
|
|
ie_tlv_iter_init(&iter, data, len);
|
|
|
|
if (!ie_tlv_iter_next(&iter))
|
|
return -EMSGSIZE;
|
|
|
|
if (ie_tlv_iter_get_tag(&iter) != IE_TYPE_BSS_LOAD)
|
|
return -EPROTOTYPE;
|
|
|
|
return ie_parse_bss_load(&iter, out_sta_count,
|
|
out_channel_utilization, out_admission_capacity);
|
|
}
|
|
|
|
int ie_parse_mobility_domain(struct ie_tlv_iter *iter, uint16_t *mdid,
|
|
bool *ft_over_ds, bool *resource_req)
|
|
{
|
|
const uint8_t *data;
|
|
|
|
if (ie_tlv_iter_get_length(iter) != 3)
|
|
return -EINVAL;
|
|
|
|
data = ie_tlv_iter_get_data(iter);
|
|
|
|
if (mdid)
|
|
*mdid = l_get_le16(data);
|
|
|
|
if (ft_over_ds)
|
|
*ft_over_ds = (data[2] & 0x01) > 0;
|
|
|
|
if (resource_req)
|
|
*resource_req = (data[2] & 0x02) > 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int ie_parse_mobility_domain_from_data(const uint8_t *data, uint8_t len,
|
|
uint16_t *mdid,
|
|
bool *ft_over_ds, bool *resource_req)
|
|
{
|
|
struct ie_tlv_iter iter;
|
|
|
|
ie_tlv_iter_init(&iter, data, len);
|
|
|
|
if (!ie_tlv_iter_next(&iter))
|
|
return -EMSGSIZE;
|
|
|
|
if (ie_tlv_iter_get_tag(&iter) != IE_TYPE_MOBILITY_DOMAIN)
|
|
return -EPROTOTYPE;
|
|
|
|
return ie_parse_mobility_domain(&iter, mdid, ft_over_ds, resource_req);
|
|
}
|
|
|
|
bool ie_build_mobility_domain(uint16_t mdid, bool ft_over_ds, bool resource_req,
|
|
uint8_t *to)
|
|
{
|
|
*to++ = IE_TYPE_MOBILITY_DOMAIN;
|
|
|
|
*to++ = 3;
|
|
|
|
l_put_le16(mdid, to);
|
|
to[2] =
|
|
(ft_over_ds ? 0x01 : 0) |
|
|
(resource_req ? 0x02 : 0);
|
|
|
|
return true;
|
|
}
|
|
|
|
int ie_parse_fast_bss_transition(struct ie_tlv_iter *iter, uint32_t mic_len,
|
|
struct ie_ft_info *info)
|
|
{
|
|
const uint8_t *data;
|
|
uint8_t len, subelem_id, subelem_len;
|
|
|
|
len = ie_tlv_iter_get_length(iter);
|
|
if (len < 66 + mic_len)
|
|
return -EINVAL;
|
|
|
|
data = ie_tlv_iter_get_data(iter);
|
|
|
|
memset(info, 0, sizeof(*info));
|
|
|
|
info->rsnxe_used = test_bit(data, 0);
|
|
info->mic_element_count = data[1];
|
|
|
|
memcpy(info->mic, data + 2, mic_len);
|
|
|
|
memcpy(info->anonce, data + mic_len + 2, 32);
|
|
|
|
memcpy(info->snonce, data + mic_len + 34, 32);
|
|
|
|
len -= 66 + mic_len;
|
|
data += 66 + mic_len;
|
|
|
|
while (len >= 2) {
|
|
subelem_id = *data++;
|
|
subelem_len = *data++;
|
|
|
|
switch (subelem_id) {
|
|
case 1:
|
|
if (subelem_len != 6)
|
|
return -EINVAL;
|
|
|
|
memcpy(info->r1khid, data, 6);
|
|
info->r1khid_present = true;
|
|
|
|
break;
|
|
|
|
case 2:
|
|
if (subelem_len < 35 || subelem_len > 51)
|
|
return -EINVAL;
|
|
|
|
info->gtk_key_id = bit_field(data[0], 0, 2);
|
|
info->gtk_len = data[2];
|
|
|
|
/*
|
|
* Check Wrapped Key field length is Key Length plus
|
|
* padding (0 - 7 bytes) plus 8 bytes for AES key wrap.
|
|
*/
|
|
if (align_len(info->gtk_len, 8) + 8 != subelem_len - 11)
|
|
return -EINVAL;
|
|
|
|
memcpy(info->gtk_rsc, data + 3, 8);
|
|
memcpy(info->gtk, data + 11, subelem_len - 11);
|
|
|
|
break;
|
|
case 3:
|
|
|
|
if (subelem_len < 1 || subelem_len > 48)
|
|
return -EINVAL;
|
|
|
|
memcpy(info->r0khid, data, subelem_len);
|
|
info->r0khid_len = subelem_len;
|
|
|
|
break;
|
|
|
|
case 4:
|
|
if (subelem_len != 33)
|
|
return -EINVAL;
|
|
|
|
info->igtk_key_id = l_get_le16(data);
|
|
memcpy(info->igtk_ipn, data + 2, 6);
|
|
info->igtk_len = data[8];
|
|
|
|
if (info->igtk_len > 16)
|
|
return -EINVAL;
|
|
|
|
memcpy(info->igtk, data + 9, subelem_len - 9);
|
|
|
|
break;
|
|
}
|
|
|
|
data += subelem_len;
|
|
len -= subelem_len + 2;
|
|
}
|
|
|
|
if (len)
|
|
return -EINVAL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int ie_parse_fast_bss_transition_from_data(const uint8_t *data, uint8_t len,
|
|
uint32_t mic_len,
|
|
struct ie_ft_info *info)
|
|
{
|
|
struct ie_tlv_iter iter;
|
|
|
|
ie_tlv_iter_init(&iter, data, len);
|
|
|
|
if (!ie_tlv_iter_next(&iter))
|
|
return -EMSGSIZE;
|
|
|
|
if (ie_tlv_iter_get_tag(&iter) != IE_TYPE_FAST_BSS_TRANSITION)
|
|
return -EPROTOTYPE;
|
|
|
|
return ie_parse_fast_bss_transition(&iter, mic_len, info);
|
|
}
|
|
|
|
bool ie_build_fast_bss_transition(const struct ie_ft_info *info,
|
|
uint32_t mic_len, uint8_t *to)
|
|
{
|
|
uint8_t *len;
|
|
|
|
*to++ = IE_TYPE_FAST_BSS_TRANSITION;
|
|
|
|
len = to++;
|
|
*len = (mic_len == 16) ? 82 : 90;
|
|
|
|
to[0] = 0x00;
|
|
to[1] = info->mic_element_count;
|
|
|
|
memcpy(to + 2, info->mic, mic_len);
|
|
|
|
memcpy(to + mic_len + 2, info->anonce, 32);
|
|
|
|
memcpy(to + mic_len + 34, info->snonce, 32);
|
|
|
|
to += (mic_len == 16) ? 82 : 90;
|
|
|
|
if (info->r1khid_present) {
|
|
to[0] = 1;
|
|
to[1] = 6;
|
|
memcpy(to + 2, info->r1khid, 6);
|
|
to += 8;
|
|
*len += 8;
|
|
}
|
|
|
|
L_WARN_ON(info->gtk_len); /* Not implemented */
|
|
|
|
if (info->r0khid_len) {
|
|
to[0] = 3;
|
|
to[1] = info->r0khid_len;
|
|
memcpy(to + 2, info->r0khid, info->r0khid_len);
|
|
to += 2 + info->r0khid_len;
|
|
*len += 2 + info->r0khid_len;
|
|
}
|
|
|
|
L_WARN_ON(info->igtk_len); /* Not implemented */
|
|
|
|
if (info->oci_present) {
|
|
to[0] = 5;
|
|
to[1] = 3;
|
|
memcpy(to + 2, info->oci, sizeof(info->oci));
|
|
*len += 5;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
enum nr_subelem_id {
|
|
NR_SUBELEM_ID_TSF_INFO = 1,
|
|
NR_SUBELEM_ID_CONDENSED_COUNTRY_STR = 2,
|
|
NR_SUBELEM_ID_BSS_TRANSITION_PREF = 3,
|
|
NR_SUBELEM_ID_BSS_TERMINATION_DURATION = 4,
|
|
NR_SUBELEM_ID_BEARING = 5,
|
|
NR_SUBELEM_ID_WIDE_BW_CHANNEL = 6,
|
|
/* Remaining defined subelements use the IE_TYPE_* ID values */
|
|
};
|
|
|
|
int ie_parse_neighbor_report(struct ie_tlv_iter *iter,
|
|
struct ie_neighbor_report_info *info)
|
|
{
|
|
unsigned int len = ie_tlv_iter_get_length(iter);
|
|
const uint8_t *data = ie_tlv_iter_get_data(iter);
|
|
struct ie_tlv_iter opt_iter;
|
|
|
|
if (len < 13)
|
|
return -EINVAL;
|
|
|
|
memset(info, 0, sizeof(*info));
|
|
|
|
memcpy(info->addr, data + 0, 6);
|
|
|
|
info->ht = test_bit(data + 8, 3);
|
|
info->md = test_bit(data + 8, 2);
|
|
info->immediate_block_ack = test_bit(data + 8, 1);
|
|
info->delayed_block_ack = test_bit(data + 8, 0);
|
|
info->rm = test_bit(data + 9, 7);
|
|
info->apsd = test_bit(data + 9, 6);
|
|
info->qos = test_bit(data + 9, 5);
|
|
info->spectrum_mgmt = test_bit(data + 9, 4);
|
|
info->key_scope = test_bit(data + 9, 3);
|
|
info->security = test_bit(data + 9, 2);
|
|
info->reachable = bit_field(data[9], 0, 2);
|
|
|
|
info->oper_class = data[10];
|
|
|
|
info->channel_num = data[11];
|
|
|
|
info->phy_type = data[12];
|
|
|
|
ie_tlv_iter_init(&opt_iter, data + 13, len - 13);
|
|
|
|
while (ie_tlv_iter_next(&opt_iter)) {
|
|
if (ie_tlv_iter_get_tag(&opt_iter) !=
|
|
NR_SUBELEM_ID_BSS_TRANSITION_PREF)
|
|
continue;
|
|
|
|
if (ie_tlv_iter_get_length(&opt_iter) != 1)
|
|
continue;
|
|
|
|
info->bss_transition_pref = ie_tlv_iter_get_data(&opt_iter)[0];
|
|
info->bss_transition_pref_present = true;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int ie_parse_roaming_consortium(struct ie_tlv_iter *iter, size_t *num_anqp_out,
|
|
const uint8_t **oi1_out, size_t *oi1_len_out,
|
|
const uint8_t **oi2_out, size_t *oi2_len_out,
|
|
const uint8_t **oi3_out, size_t *oi3_len_out)
|
|
{
|
|
unsigned int len = ie_tlv_iter_get_length(iter);
|
|
const uint8_t *data = ie_tlv_iter_get_data(iter);
|
|
size_t num_anqp;
|
|
size_t oi1_len;
|
|
size_t oi2_len;
|
|
size_t oi3_len;
|
|
|
|
if (len < 4)
|
|
return -EINVAL;
|
|
|
|
num_anqp = l_get_u8(data);
|
|
oi1_len = bit_field(l_get_u8(data + 1), 0, 4);
|
|
oi2_len = bit_field(l_get_u8(data + 1), 4, 4);
|
|
oi3_len = len - (2 + oi1_len + oi2_len);
|
|
|
|
if (!oi1_len)
|
|
return -EINVAL;
|
|
|
|
if (len < oi1_len + oi2_len + oi3_len + 2)
|
|
return -EINVAL;
|
|
|
|
if (num_anqp_out)
|
|
*num_anqp_out = num_anqp;
|
|
|
|
if (oi1_out)
|
|
*oi1_out = data + 2;
|
|
|
|
if (oi1_len_out)
|
|
*oi1_len_out = oi1_len;
|
|
|
|
/* OI2/3 are optional, explicitly set to NULL if not included */
|
|
if (oi2_len) {
|
|
if (oi2_out)
|
|
*oi2_out = data + 2 + oi1_len;
|
|
|
|
if (oi2_len_out)
|
|
*oi2_len_out = oi2_len;
|
|
} else if (oi2_out)
|
|
*oi2_out = NULL;
|
|
|
|
if (oi3_len) {
|
|
if (oi3_out)
|
|
*oi3_out = data + 2 + oi1_len + oi2_len;
|
|
|
|
if (oi3_len_out)
|
|
*oi3_len_out = oi3_len;
|
|
} else if (oi3_out)
|
|
*oi3_out = NULL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int ie_parse_roaming_consortium_from_data(const uint8_t *data, size_t len,
|
|
size_t *num_anqp_out, const uint8_t **oi1_out,
|
|
size_t *oi1_len_out, const uint8_t **oi2_out,
|
|
size_t *oi2_len_out, const uint8_t **oi3_out,
|
|
size_t *oi3_len_out)
|
|
{
|
|
struct ie_tlv_iter iter;
|
|
|
|
ie_tlv_iter_init(&iter, data, len);
|
|
|
|
if (!ie_tlv_iter_next(&iter))
|
|
return -EMSGSIZE;
|
|
|
|
if (ie_tlv_iter_get_tag(&iter) != IE_TYPE_ROAMING_CONSORTIUM)
|
|
return -EPROTOTYPE;
|
|
|
|
return ie_parse_roaming_consortium(&iter, num_anqp_out, oi1_out,
|
|
oi1_len_out, oi2_out,
|
|
oi2_len_out, oi3_out,
|
|
oi3_len_out);
|
|
}
|
|
|
|
int ie_build_roaming_consortium(const uint8_t *rc, size_t rc_len, uint8_t *to)
|
|
{
|
|
*to++ = IE_TYPE_VENDOR_SPECIFIC;
|
|
|
|
*to++ = rc_len + 4;
|
|
|
|
memcpy(to, wifi_alliance_oui, 3);
|
|
to += 3;
|
|
|
|
*to++ = 0x1d;
|
|
|
|
memcpy(to, rc, rc_len);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int ie_parse_hs20_indication(struct ie_tlv_iter *iter, uint8_t *version_out,
|
|
uint16_t *pps_mo_id_out, uint8_t *domain_id_out,
|
|
bool *dgaf_disable_out)
|
|
{
|
|
unsigned int len = ie_tlv_iter_get_length(iter);
|
|
const uint8_t *data = ie_tlv_iter_get_data(iter);
|
|
uint8_t hs20_config;
|
|
bool pps_mo_present, domain_id_present;
|
|
|
|
if (!is_ie_wfa_ie(data, iter->len, IE_WFA_OI_HS20_INDICATION))
|
|
return -EPROTOTYPE;
|
|
|
|
hs20_config = l_get_u8(data + 4);
|
|
|
|
pps_mo_present = test_bit(&hs20_config, 1);
|
|
domain_id_present = test_bit(&hs20_config, 2);
|
|
|
|
/*
|
|
* Hotspot 2.0 Spec - Section 3.1.1
|
|
*
|
|
* "Either the PPS MO ID field or the ANQP Domain ID field (these
|
|
* are mutually exclusive fields) is included in the HS2.0 Indication
|
|
* element"
|
|
*/
|
|
if (pps_mo_present && domain_id_present)
|
|
return -EPROTOTYPE;
|
|
|
|
if (dgaf_disable_out)
|
|
*dgaf_disable_out = test_bit(&hs20_config, 0);
|
|
|
|
if (version_out)
|
|
*version_out = bit_field(hs20_config, 4, 4);
|
|
|
|
if (pps_mo_id_out)
|
|
*pps_mo_id_out = 0;
|
|
|
|
if (domain_id_out)
|
|
*domain_id_out = 0;
|
|
|
|
/* No PPS MO ID or Domain ID */
|
|
if (len == 5)
|
|
return 0;
|
|
|
|
/* we know from is_ie_wfa_ie that the length must be 7 */
|
|
if (pps_mo_present) {
|
|
if (pps_mo_id_out)
|
|
*pps_mo_id_out = l_get_u16(data + 5);
|
|
} else if (domain_id_present) {
|
|
if (domain_id_out)
|
|
*domain_id_out = l_get_u16(data + 5);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int ie_parse_hs20_indication_from_data(const uint8_t *data, size_t len,
|
|
uint8_t *version, uint16_t *pps_mo_id,
|
|
uint8_t *domain_id, bool *dgaf_disable)
|
|
{
|
|
struct ie_tlv_iter iter;
|
|
|
|
ie_tlv_iter_init(&iter, data, len);
|
|
|
|
if (!ie_tlv_iter_next(&iter))
|
|
return -EMSGSIZE;
|
|
|
|
if (ie_tlv_iter_get_tag(&iter) != IE_TYPE_VENDOR_SPECIFIC)
|
|
return -EPROTOTYPE;
|
|
|
|
return ie_parse_hs20_indication(&iter, version, pps_mo_id, domain_id,
|
|
dgaf_disable);
|
|
}
|
|
|
|
/*
|
|
* Only use version for building as this is meant for the (Re)Association IE.
|
|
* In this case DGAF is always disabled, Domain ID should not be present, and
|
|
* this device was not configured with PerProviderSubscription MO.
|
|
*/
|
|
int ie_build_hs20_indication(uint8_t version, uint8_t *to)
|
|
{
|
|
if (version > 2)
|
|
return -EINVAL;
|
|
|
|
*to++ = IE_TYPE_VENDOR_SPECIFIC;
|
|
*to++ = 5;
|
|
|
|
memcpy(to, wifi_alliance_oui, 3);
|
|
to += 3;
|
|
|
|
*to++ = IE_WFA_OI_HS20_INDICATION;
|
|
|
|
*to++ = (version << 4) & 0xf0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
bool ie_rsnxe_capable(const uint8_t *rsnxe, unsigned int bit)
|
|
{
|
|
unsigned int field_len;
|
|
|
|
if (!rsnxe)
|
|
return false;
|
|
|
|
if (rsnxe[1] == 0)
|
|
return false;
|
|
|
|
field_len = bit_field(rsnxe[2], 0, 4);
|
|
if (field_len + 1 != rsnxe[1])
|
|
return false;
|
|
|
|
if ((bit / 8) > field_len)
|
|
return false;
|
|
|
|
return test_bit(rsnxe + 2, bit);
|
|
}
|
|
|
|
/* 802.11ai-2016 Tables 9-589r, 9-262d, 9-262e */
|
|
enum ie_fils_ip_addr_req_ctrl_bits {
|
|
IE_FILS_IP_ADDR_REQ_CTRL_IPV4_MASK = 3 << 0,
|
|
IE_FILS_IP_ADDR_REQ_CTRL_IPV4_NONE = 0 << 0,
|
|
IE_FILS_IP_ADDR_REQ_CTRL_IPV4_NEW = 2 << 0,
|
|
IE_FILS_IP_ADDR_REQ_CTRL_IPV4_SPECIFIC = 3 << 0,
|
|
IE_FILS_IP_ADDR_REQ_CTRL_IPV6_MASK = 3 << 2,
|
|
IE_FILS_IP_ADDR_REQ_CTRL_IPV6_NONE = 0 << 2,
|
|
IE_FILS_IP_ADDR_REQ_CTRL_IPV6_NEW = 2 << 2,
|
|
IE_FILS_IP_ADDR_REQ_CTRL_IPV6_SPECIFIC = 3 << 2,
|
|
IE_FILS_IP_ADDR_REQ_CTRL_DNS = 1 << 4,
|
|
};
|
|
|
|
/* 802.11ai-2016 Table 9-262f */
|
|
enum ie_fils_ip_addr_resp_ctrl_bits {
|
|
IE_FILS_IP_ADDR_RESP_CTRL_IP_PENDING = 1 << 0,
|
|
IE_FILS_IP_ADDR_RESP_CTRL_IPV4_ASSIGNED = 1 << 1,
|
|
IE_FILS_IP_ADDR_RESP_CTRL_IPV4_GW_INCLUDED = 1 << 2,
|
|
IE_FILS_IP_ADDR_RESP_CTRL_IPV6_ASSIGNED = 1 << 3,
|
|
IE_FILS_IP_ADDR_RESP_CTRL_IPV6_GW_INCLUDED = 1 << 4,
|
|
IE_FILS_IP_ADDR_RESP_CTRL_IPV4_LIFETIME_INCLUDED = 1 << 5,
|
|
IE_FILS_IP_ADDR_RESP_CTRL_IPV6_LIFETIME_INCLUDED = 1 << 6,
|
|
};
|
|
|
|
/* 802.11ai-2016 Table 9-262h */
|
|
enum ie_fils_ip_addr_resp_dns_ctrl_bits {
|
|
IE_FILS_IP_ADDR_RESP_DNS_CTRL_IPV4_DNS_INCLUDED = 1 << 0,
|
|
IE_FILS_IP_ADDR_RESP_DNS_CTRL_IPV6_DNS_INCLUDED = 1 << 1,
|
|
IE_FILS_IP_ADDR_RESP_DNS_CTRL_IPV4_DNS_MAC_INCLUDED = 1 << 2,
|
|
IE_FILS_IP_ADDR_RESP_DNS_CTRL_IPV6_DNS_MAC_INCLUDED = 1 << 3,
|
|
};
|
|
|
|
int ie_parse_fils_ip_addr_request(struct ie_tlv_iter *iter,
|
|
struct ie_fils_ip_addr_request_info *out)
|
|
{
|
|
unsigned int len = ie_tlv_iter_get_length(iter);
|
|
const uint8_t *data = ie_tlv_iter_get_data(iter);
|
|
struct ie_fils_ip_addr_request_info info = {};
|
|
bool ipv4_specific_addr = false;
|
|
bool ipv6_specific_addr = false;
|
|
|
|
if (len < 1)
|
|
return -EMSGSIZE;
|
|
|
|
if (L_IN_SET(data[0] & IE_FILS_IP_ADDR_REQ_CTRL_IPV4_MASK,
|
|
IE_FILS_IP_ADDR_REQ_CTRL_IPV4_NEW,
|
|
IE_FILS_IP_ADDR_REQ_CTRL_IPV4_SPECIFIC)) {
|
|
info.ipv4 = true;
|
|
ipv4_specific_addr =
|
|
(data[0] & IE_FILS_IP_ADDR_REQ_CTRL_IPV4_MASK) ==
|
|
IE_FILS_IP_ADDR_REQ_CTRL_IPV4_SPECIFIC;
|
|
} else if ((data[0] & IE_FILS_IP_ADDR_REQ_CTRL_IPV4_MASK) !=
|
|
IE_FILS_IP_ADDR_REQ_CTRL_IPV4_NONE)
|
|
return -EINVAL;
|
|
|
|
if (L_IN_SET(data[0] & IE_FILS_IP_ADDR_REQ_CTRL_IPV6_MASK,
|
|
IE_FILS_IP_ADDR_REQ_CTRL_IPV6_NEW,
|
|
IE_FILS_IP_ADDR_REQ_CTRL_IPV6_SPECIFIC)) {
|
|
info.ipv6 = true;
|
|
ipv6_specific_addr =
|
|
(data[0] & IE_FILS_IP_ADDR_REQ_CTRL_IPV6_MASK) ==
|
|
IE_FILS_IP_ADDR_REQ_CTRL_IPV6_SPECIFIC;
|
|
} else if ((data[0] & IE_FILS_IP_ADDR_REQ_CTRL_IPV6_MASK) !=
|
|
IE_FILS_IP_ADDR_REQ_CTRL_IPV6_NONE)
|
|
return -EINVAL;
|
|
|
|
info.dns = !!(*data++ & IE_FILS_IP_ADDR_REQ_CTRL_DNS);
|
|
|
|
if (len < 1 + (ipv4_specific_addr ? 4u : 0u) +
|
|
(ipv6_specific_addr ? 16u : 0u))
|
|
return -EMSGSIZE;
|
|
|
|
if (ipv4_specific_addr) {
|
|
info.ipv4_requested_addr = l_get_u32(data);
|
|
data += 4;
|
|
|
|
if (!info.ipv4_requested_addr)
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (ipv6_specific_addr) {
|
|
memcpy(info.ipv6_requested_addr, data, 16);
|
|
data += 16;
|
|
|
|
if (l_memeqzero(info.ipv6_requested_addr, 16))
|
|
return -EINVAL;
|
|
}
|
|
|
|
memcpy(out, &info, sizeof(info));
|
|
return 0;
|
|
}
|
|
|
|
void ie_build_fils_ip_addr_request(
|
|
const struct ie_fils_ip_addr_request_info *info,
|
|
uint8_t *to)
|
|
{
|
|
uint8_t *len;
|
|
uint8_t *ctrl;
|
|
|
|
*to++ = IE_TYPE_EXTENSION;
|
|
len = to++;
|
|
*to++ = IE_TYPE_FILS_IP_ADDRESS & 0xff;
|
|
ctrl = to++;
|
|
|
|
*ctrl = info->dns ? IE_FILS_IP_ADDR_REQ_CTRL_DNS : 0;
|
|
|
|
if (info->ipv4) {
|
|
if (info->ipv4_requested_addr) {
|
|
l_put_u32(info->ipv4_requested_addr, to);
|
|
to += 4;
|
|
*ctrl |= IE_FILS_IP_ADDR_REQ_CTRL_IPV4_SPECIFIC;
|
|
} else
|
|
*ctrl |= IE_FILS_IP_ADDR_REQ_CTRL_IPV4_NEW;
|
|
}
|
|
|
|
if (info->ipv6) {
|
|
if (!l_memeqzero(info->ipv6_requested_addr, 16)) {
|
|
memcpy(to, info->ipv6_requested_addr, 16);
|
|
to += 16;
|
|
*ctrl |= IE_FILS_IP_ADDR_REQ_CTRL_IPV6_SPECIFIC;
|
|
} else
|
|
*ctrl |= IE_FILS_IP_ADDR_REQ_CTRL_IPV6_NEW;
|
|
}
|
|
|
|
*len = to - (len + 1);
|
|
}
|
|
|
|
#define NEXT_FIELD(data, len, size) (__extension__ ({ \
|
|
const uint8_t *_ptr = data; \
|
|
\
|
|
if (len < size) \
|
|
return -EMSGSIZE; \
|
|
\
|
|
data += size; \
|
|
len -= size; \
|
|
_ptr; }))
|
|
|
|
int ie_parse_fils_ip_addr_response(struct ie_tlv_iter *iter,
|
|
struct ie_fils_ip_addr_response_info *out)
|
|
{
|
|
unsigned int len = ie_tlv_iter_get_length(iter);
|
|
const uint8_t *data = ie_tlv_iter_get_data(iter);
|
|
struct ie_fils_ip_addr_response_info info = {};
|
|
const uint8_t *resp_ctrl;
|
|
const uint8_t *dns_ctrl;
|
|
const uint8_t *ptr;
|
|
|
|
resp_ctrl = NEXT_FIELD(data, len, 2);
|
|
dns_ctrl = resp_ctrl + 1;
|
|
|
|
info.response_pending =
|
|
!!(*resp_ctrl & IE_FILS_IP_ADDR_RESP_CTRL_IP_PENDING);
|
|
|
|
if (info.response_pending) {
|
|
info.response_timeout =
|
|
bit_field(*resp_ctrl, 1, 6); /* seconds */
|
|
return 0;
|
|
}
|
|
|
|
if (*resp_ctrl & IE_FILS_IP_ADDR_RESP_CTRL_IPV4_ASSIGNED) {
|
|
uint32_t netmask;
|
|
|
|
ptr = NEXT_FIELD(data, len, 8);
|
|
info.ipv4_addr = l_get_u32(ptr);
|
|
netmask = l_get_be32(ptr + 4);
|
|
info.ipv4_prefix_len = __builtin_popcount(netmask);
|
|
|
|
if (!info.ipv4_addr || info.ipv4_prefix_len > 30 || netmask !=
|
|
util_netmask_from_prefix(info.ipv4_prefix_len))
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (*resp_ctrl & IE_FILS_IP_ADDR_RESP_CTRL_IPV4_GW_INCLUDED) {
|
|
ptr = NEXT_FIELD(data, len, 10);
|
|
info.ipv4_gateway = l_get_u32(ptr);
|
|
memcpy(info.ipv4_gateway_mac, ptr + 4, 6);
|
|
|
|
/* Check gateway is on the same subnet */
|
|
if (info.ipv4_addr &&
|
|
!util_ip_subnet_match(info.ipv4_prefix_len,
|
|
&info.ipv4_addr,
|
|
&info.ipv4_gateway))
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (*resp_ctrl & IE_FILS_IP_ADDR_RESP_CTRL_IPV6_ASSIGNED) {
|
|
ptr = NEXT_FIELD(data, len, 17);
|
|
memcpy(info.ipv6_addr, ptr, 16);
|
|
info.ipv6_prefix_len = ptr[16];
|
|
|
|
if (l_memeqzero(info.ipv6_addr, 16) ||
|
|
info.ipv6_prefix_len > 126)
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (*resp_ctrl & IE_FILS_IP_ADDR_RESP_CTRL_IPV6_GW_INCLUDED) {
|
|
ptr = NEXT_FIELD(data, len, 22);
|
|
memcpy(info.ipv6_gateway, ptr, 16);
|
|
memcpy(info.ipv6_gateway_mac, ptr + 16, 6);
|
|
|
|
/* Check gateway is on the same subnet */
|
|
if (!l_memeqzero(info.ipv6_addr, 16) &&
|
|
!util_ip_subnet_match(info.ipv6_prefix_len,
|
|
info.ipv6_addr,
|
|
info.ipv6_gateway))
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (*resp_ctrl & IE_FILS_IP_ADDR_RESP_CTRL_IPV4_LIFETIME_INCLUDED)
|
|
info.ipv4_lifetime = *NEXT_FIELD(data, len, 1); /* seconds */
|
|
|
|
if (*resp_ctrl & IE_FILS_IP_ADDR_RESP_CTRL_IPV6_LIFETIME_INCLUDED)
|
|
info.ipv6_lifetime = *NEXT_FIELD(data, len, 1); /* seconds */
|
|
|
|
if (*dns_ctrl & IE_FILS_IP_ADDR_RESP_DNS_CTRL_IPV4_DNS_INCLUDED) {
|
|
info.ipv4_dns = l_get_u32(NEXT_FIELD(data, len, 4));
|
|
|
|
if (!info.ipv4_dns)
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (*dns_ctrl & IE_FILS_IP_ADDR_RESP_DNS_CTRL_IPV6_DNS_INCLUDED) {
|
|
memcpy(info.ipv6_dns, NEXT_FIELD(data, len, 16), 16);
|
|
|
|
if (l_memeqzero(info.ipv6_dns, 16))
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (*dns_ctrl & IE_FILS_IP_ADDR_RESP_DNS_CTRL_IPV4_DNS_MAC_INCLUDED)
|
|
memcpy(info.ipv4_dns_mac, NEXT_FIELD(data, len, 6), 6);
|
|
|
|
if (*dns_ctrl & IE_FILS_IP_ADDR_RESP_DNS_CTRL_IPV6_DNS_MAC_INCLUDED)
|
|
memcpy(info.ipv6_dns_mac, NEXT_FIELD(data, len, 6), 6);
|
|
|
|
memcpy(out, &info, sizeof(info));
|
|
return 0;
|
|
}
|
|
|
|
void ie_build_fils_ip_addr_response(
|
|
const struct ie_fils_ip_addr_response_info *info,
|
|
uint8_t *to)
|
|
{
|
|
uint8_t *len;
|
|
uint8_t *resp_ctrl;
|
|
uint8_t *dns_ctrl;
|
|
|
|
*to++ = IE_TYPE_EXTENSION;
|
|
len = to++;
|
|
*to++ = IE_TYPE_FILS_IP_ADDRESS & 0xff;
|
|
resp_ctrl = to++;
|
|
dns_ctrl = to++;
|
|
|
|
*resp_ctrl = 0;
|
|
*dns_ctrl = 0;
|
|
|
|
if (info->response_pending) {
|
|
*resp_ctrl |= IE_FILS_IP_ADDR_RESP_CTRL_IP_PENDING;
|
|
*resp_ctrl |= info->response_timeout << 1;
|
|
goto done;
|
|
}
|
|
|
|
if (info->ipv4_addr) {
|
|
uint32_t netmask =
|
|
util_netmask_from_prefix(info->ipv4_prefix_len);
|
|
|
|
*resp_ctrl |= IE_FILS_IP_ADDR_RESP_CTRL_IPV4_ASSIGNED;
|
|
|
|
l_put_u32(info->ipv4_addr, to);
|
|
l_put_u32(htonl(netmask), to + 4);
|
|
to += 8;
|
|
}
|
|
|
|
if (info->ipv4_gateway) {
|
|
*resp_ctrl |= IE_FILS_IP_ADDR_RESP_CTRL_IPV4_GW_INCLUDED;
|
|
|
|
l_put_u32(info->ipv4_gateway, to);
|
|
memcpy(to + 4, info->ipv4_gateway_mac, 6);
|
|
to += 10;
|
|
}
|
|
|
|
if (!l_memeqzero(info->ipv6_addr, 16)) {
|
|
*resp_ctrl |= IE_FILS_IP_ADDR_RESP_CTRL_IPV6_ASSIGNED;
|
|
|
|
memcpy(to, info->ipv6_addr, 16);
|
|
to[16] = info->ipv6_prefix_len;
|
|
to += 17;
|
|
}
|
|
|
|
if (!l_memeqzero(info->ipv6_gateway, 16)) {
|
|
*resp_ctrl |= IE_FILS_IP_ADDR_RESP_CTRL_IPV6_GW_INCLUDED;
|
|
|
|
memcpy(to, info->ipv6_gateway, 16);
|
|
memcpy(to + 16, info->ipv6_gateway_mac, 6);
|
|
to += 22;
|
|
}
|
|
|
|
if (info->ipv4_lifetime) {
|
|
*resp_ctrl |= IE_FILS_IP_ADDR_RESP_CTRL_IPV4_LIFETIME_INCLUDED;
|
|
|
|
*to++ = info->ipv4_lifetime;
|
|
}
|
|
|
|
if (info->ipv6_lifetime) {
|
|
*resp_ctrl |= IE_FILS_IP_ADDR_RESP_CTRL_IPV6_LIFETIME_INCLUDED;
|
|
|
|
*to++ = info->ipv6_lifetime;
|
|
}
|
|
|
|
if (info->ipv4_dns) {
|
|
*dns_ctrl |= IE_FILS_IP_ADDR_RESP_DNS_CTRL_IPV4_DNS_INCLUDED;
|
|
|
|
l_put_u32(info->ipv4_dns, to);
|
|
to += 4;
|
|
}
|
|
|
|
if (!l_memeqzero(info->ipv6_dns, 16)) {
|
|
*dns_ctrl |= IE_FILS_IP_ADDR_RESP_DNS_CTRL_IPV6_DNS_INCLUDED;
|
|
|
|
memcpy(to, info->ipv6_dns, 16);
|
|
to += 16;
|
|
}
|
|
|
|
if (!l_memeqzero(info->ipv4_dns_mac, 6)) {
|
|
*dns_ctrl |=
|
|
IE_FILS_IP_ADDR_RESP_DNS_CTRL_IPV4_DNS_MAC_INCLUDED;
|
|
|
|
memcpy(to, info->ipv4_dns_mac, 6);
|
|
to += 6;
|
|
}
|
|
|
|
if (!l_memeqzero(info->ipv6_dns_mac, 6)) {
|
|
*dns_ctrl |=
|
|
IE_FILS_IP_ADDR_RESP_DNS_CTRL_IPV6_DNS_MAC_INCLUDED;
|
|
|
|
memcpy(to, info->ipv6_dns_mac, 6);
|
|
to += 6;
|
|
}
|
|
|
|
done:
|
|
*len = to - (len + 1);
|
|
}
|
|
|
|
/*
|
|
* Parse Network Cost IE according to:
|
|
* https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nct/88f0cdf4-cdf2-4455-b849-4abf1e5c11ac
|
|
*/
|
|
int ie_parse_network_cost(const void *data, size_t len,
|
|
uint16_t *level, uint16_t *flags)
|
|
{
|
|
const uint8_t *ie = data;
|
|
|
|
if (len < 10 || ie[0] != IE_TYPE_VENDOR_SPECIFIC || ie[1] != 8)
|
|
return -ENOMSG;
|
|
|
|
if (memcmp(ie + 2, microsoft_oui, 3) || ie[5] != 0x11)
|
|
return -ENOMSG;
|
|
|
|
*level = l_get_le16(ie + 6);
|
|
*flags = l_get_le16(ie + 8);
|
|
return 0;
|
|
}
|
|
|
|
int ie_parse_owe_transition(const void *data, size_t len,
|
|
struct ie_owe_transition_info *info)
|
|
{
|
|
const uint8_t *ie = data;
|
|
const uint8_t *bssid;
|
|
const uint8_t *ssid;
|
|
uint8_t oper_class = 0;
|
|
uint8_t channel = 0;
|
|
size_t slen;
|
|
|
|
if (len < 14 || ie[0] != IE_TYPE_VENDOR_SPECIFIC)
|
|
return -ENOMSG;
|
|
|
|
if (!is_ie_wfa_ie(ie + 2, len - 2, IE_WFA_OI_OWE_TRANSITION))
|
|
return -ENOMSG;
|
|
|
|
slen = l_get_u8(ie + 12);
|
|
if (slen > 32)
|
|
return -ENOMSG;
|
|
|
|
/*
|
|
* WFA OWE Specification 2.3.1
|
|
*
|
|
* "Band Info and Channel Info are optional fields. If configured,
|
|
* both fields shall be included in an OWE Transition Mode element"
|
|
*/
|
|
if (len != slen + 13 && len != slen + 15)
|
|
return -ENOMSG;
|
|
|
|
bssid = ie + 6;
|
|
ssid = ie + 13;
|
|
|
|
if (len == slen + 15) {
|
|
oper_class = l_get_u8(ie + 13 + slen);
|
|
channel = l_get_u8(ie + 14 + slen);
|
|
}
|
|
|
|
memcpy(info->bssid, bssid, 6);
|
|
memcpy(info->ssid, ssid, slen);
|
|
info->ssid_len = slen;
|
|
info->oper_class = oper_class;
|
|
info->channel = channel;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int ie_parse_oci(const void *data, size_t len, const uint8_t **oci)
|
|
{
|
|
struct ie_tlv_iter iter;
|
|
|
|
ie_tlv_iter_init(&iter, data, len);
|
|
|
|
if (!ie_tlv_iter_next(&iter))
|
|
return -EMSGSIZE;
|
|
|
|
if (ie_tlv_iter_get_length(&iter) != 3)
|
|
return -EMSGSIZE;
|
|
|
|
if (ie_tlv_iter_get_tag(&iter) != IE_TYPE_OCI)
|
|
return -EPROTOTYPE;
|
|
|
|
*oci = ie_tlv_iter_get_data(&iter);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Checks the supported width set (Table 9-322b) meets the following
|
|
* requirements:
|
|
* - B0 and bits B1/B2/B3 are mutually exclusive.
|
|
* - B2 is only set if B1 is set
|
|
* - B3 is only set if B2 is set (and in turn, B1 is set)
|
|
* - The IE length supports B2 and B3 MCS sets
|
|
*/
|
|
bool ie_validate_he_capabilities(const void *data, size_t len)
|
|
{
|
|
uint8_t width_set;
|
|
const uint8_t *ptr = data;
|
|
bool freq_2_4;
|
|
bool width_40_80;
|
|
bool width_160;
|
|
bool width_80p80;
|
|
|
|
if (len < 22)
|
|
return false;
|
|
|
|
width_set = bit_field((ptr + 7)[0], 1, 7);
|
|
|
|
/* B0 indicates support for 40MHz, but only in 2.4GHz band */
|
|
freq_2_4 = test_bit(&width_set, 0);
|
|
|
|
/* B1 indicates support for 40/80MHz */
|
|
width_40_80 = test_bit(&width_set, 1);
|
|
|
|
if (width_40_80 && freq_2_4)
|
|
return false;
|
|
|
|
/* B2 indicates support for 160MHz MCS table */
|
|
width_160 = test_bit(&width_set, 2);
|
|
|
|
/* Ensure B1 is set, not B0, and the length includes this MCS table */
|
|
if (width_160 && (!width_40_80 || freq_2_4 || len < 26))
|
|
return false;
|
|
|
|
/* B3 indicates support for 80+80Mhz MCS table */
|
|
width_80p80 = test_bit(&width_set, 3);
|
|
|
|
/* Ensure B2 is set, not B0, and the length includes this MCS table */
|
|
if (width_80p80 && (!width_160 || freq_2_4 || len < 30))
|
|
return false;
|
|
|
|
return true;
|
|
}
|