3
0
mirror of https://git.kernel.org/pub/scm/network/wireless/iwd.git synced 2025-04-09 07:58:09 +02:00
iwd/src/ie.c
Denis Kenzior e565b75032 defs: Add defs.h to hold certain global definitions
This will help to get rid of magic number use throughout the project.
The definitions should be limited to global magic numbers that are used
throughout the project, for example SSID length, MAC address length,
etc.
2024-08-23 11:17:20 -05:00

2741 lines
62 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;
}
uint32_t ie_rsn_akm_suite_to_akm(enum ie_rsn_akm_suite akm)
{
switch (akm) {
case IE_RSN_AKM_SUITE_8021X:
return CRYPTO_AKM_8021X;
case IE_RSN_AKM_SUITE_PSK:
return CRYPTO_AKM_PSK;
case IE_RSN_AKM_SUITE_FT_OVER_8021X:
return CRYPTO_AKM_FT_OVER_8021X;
case IE_RSN_AKM_SUITE_FT_USING_PSK:
return CRYPTO_AKM_FT_USING_PSK;
case IE_RSN_AKM_SUITE_8021X_SHA256:
return CRYPTO_AKM_8021X_SHA256;
case IE_RSN_AKM_SUITE_PSK_SHA256:
return CRYPTO_AKM_PSK_SHA256;
case IE_RSN_AKM_SUITE_TDLS:
return CRYPTO_AKM_TDLS;
case IE_RSN_AKM_SUITE_SAE_SHA256:
return CRYPTO_AKM_SAE_SHA256;
case IE_RSN_AKM_SUITE_FT_OVER_SAE_SHA256:
return CRYPTO_AKM_FT_OVER_SAE_SHA256;
case IE_RSN_AKM_SUITE_AP_PEER_KEY_SHA256:
return CRYPTO_AKM_AP_PEER_KEY_SHA256;
case IE_RSN_AKM_SUITE_8021X_SUITE_B_SHA256:
return CRYPTO_AKM_8021X_SUITE_B_SHA256;
case IE_RSN_AKM_SUITE_8021X_SUITE_B_SHA384:
return CRYPTO_AKM_8021X_SUITE_B_SHA384;
case IE_RSN_AKM_SUITE_FT_OVER_8021X_SHA384:
return CRYPTO_AKM_FT_OVER_8021X_SHA384;
case IE_RSN_AKM_SUITE_FILS_SHA256:
return CRYPTO_AKM_FILS_SHA256;
case IE_RSN_AKM_SUITE_FILS_SHA384:
return CRYPTO_AKM_FILS_SHA384;
case IE_RSN_AKM_SUITE_FT_OVER_FILS_SHA256:
return CRYPTO_AKM_FT_OVER_FILS_SHA256;
case IE_RSN_AKM_SUITE_FT_OVER_FILS_SHA384:
return CRYPTO_AKM_FT_OVER_FILS_SHA384;
case IE_RSN_AKM_SUITE_OWE:
return CRYPTO_AKM_OWE;
case IE_RSN_AKM_SUITE_OSEN:
return CRYPTO_AKM_OSEN;
}
return 0;
}
/* 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;
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;
}
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 > SSID_MAX_SIZE)
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;
}