anqp: added utility for parsing ANQP responses

Currently these are geared to support the WiFi Alliance Hotspot 2.0
ANQP elements, which all fall under the vendor specific ANQP element.

anqp_iter_next behaves similar to the genl parsers, where the id, length
and data will be returned as out parameters. Currently there is only
vendor support for Hotspot 2.0. anqp_iter_is_hs20 can be used to setup
the subtype, length, and data pointer to parse any Hotspot 2.0 ANQP
elements. From here the subtype can be checked and a vendor specific
parser for that subtype can be used to parse the data, e.g.
hs20_parse_osu_provider_nai.
This commit is contained in:
James Prestwood 2019-06-14 13:12:05 -07:00 committed by Denis Kenzior
parent 77a6b49803
commit 2ce5277f6d
3 changed files with 479 additions and 0 deletions

View File

@ -205,6 +205,7 @@ src_iwd_SOURCES = src/main.c linux/nl80211.h src/iwd.h src/missing.h \
src/fils.h src/fils.c \
src/rtnlutil.h src/rtnlutil.c \
src/auth-proto.h \
src/anqp.h src/anqp.c \
$(eap_sources) \
$(builtin_sources)
src_iwd_LDADD = $(ell_ldadd) -ldl

361
src/anqp.c Normal file
View File

@ -0,0 +1,361 @@
/*
*
* Wireless daemon for Linux
*
* Copyright (C) 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 <stdint.h>
#include <ell/ell.h>
#include "src/anqp.h"
#include "src/util.h"
#include "src/eap-private.h"
static const uint8_t wifi_alliance_oui[3] = { 0x50, 0x6f, 0x9a };
void anqp_iter_init(struct anqp_iter *iter, const unsigned char *anqp,
unsigned int len)
{
iter->anqp = anqp;
iter->max = len;
iter->pos = 0;
}
bool anqp_iter_next(struct anqp_iter *iter, uint16_t *id, uint16_t *len,
const void **data)
{
const unsigned char *anqp = iter->anqp + iter->pos;
const unsigned char *end = iter->anqp + iter->max;
if (iter->pos + 4 >= iter->max)
return false;
if (anqp + l_get_le16(anqp + 2) > end)
return false;
*id = l_get_le16(anqp);
anqp += 2;
*len = l_get_le16(anqp);
anqp += 2;
*data = anqp;
iter->id = *id;
iter->len = *len;
iter->data = *data;
iter->pos = anqp + *len - iter->anqp;
return true;
}
bool anqp_hs20_parse_osu_provider_nai(const unsigned char *anqp,
unsigned int len, const char **nai_out)
{
uint8_t nai_len;
static char nai[256] = { 0 };
if (len < 1)
return false;
nai_len = *anqp++;
len--;
if (len < nai_len)
return false;
memcpy(nai, anqp, nai_len);
*nai_out = nai;
return true;
}
bool anqp_iter_is_hs20(const struct anqp_iter *iter, uint8_t *stype,
unsigned int *len, const unsigned char **data)
{
const unsigned char *anqp = iter->data;
unsigned int anqp_len = iter->len;
uint8_t type;
if (iter->len < 6)
return false;
if (memcmp(anqp, wifi_alliance_oui, 3))
return false;
anqp += 3;
anqp_len -= 3;
type = *anqp++;
anqp_len--;
if (type != 0x11)
return false;
*stype = *anqp++;
anqp_len--;
/* reserved byte */
anqp++;
anqp_len--;
*data = anqp;
*len = anqp_len;
return true;
}
static bool parse_eap_params(const unsigned char *anqp, unsigned int len,
uint8_t *method, uint8_t *non_eap_inner,
uint8_t *eap_inner, uint8_t *credential,
uint8_t *tunneled_credential)
{
uint8_t param_count;
if (len < 2)
return false;
*method = *anqp++;
param_count = *anqp++;
len -= 2;
*non_eap_inner = 0;
*eap_inner = 0;
*credential = 0;
*tunneled_credential = 0;
while (param_count--) {
uint8_t ap_id;
uint8_t ap_len;
if (len < 2)
return false;
ap_id = *anqp++;
ap_len = *anqp++;
len -= 2;
if (len < ap_len)
return false;
switch (ap_id) {
case ANQP_AP_NON_INNER_AUTH_EAP:
*non_eap_inner = *anqp;
break;
case ANQP_AP_INNER_AUTH_EAP:
*eap_inner = *anqp;
break;
case ANQP_AP_CREDENTIAL:
*credential = *anqp;
break;
case ANQP_AP_TUNNELED_EAP_CREDENTIAL:
*tunneled_credential = *anqp;
break;
case ANQP_AP_EXPANDED_EAP_METHOD:
case ANQP_AP_EXPANDED_INNER_EAP_METHOD:
case ANQP_AP_VENDOR_SPECIFIC:
break;
}
anqp += ap_len;
len -= ap_len;
}
return true;
}
/*
* Parses an EAP ANQP list.
*/
static bool parse_eap(const unsigned char *anqp, unsigned int len,
const char *nai, bool hs20,
struct anqp_eap_method *method_out)
{
uint8_t eap_count;
if (len < 1)
return false;
eap_count = *anqp++;
len--;
while (eap_count--) {
uint8_t eap_len;
uint8_t method;
uint8_t non_eap_inner;
uint8_t eap_inner;
uint8_t credential;
uint8_t tunneled_credential;
if (len < 1)
return false;
eap_len = *anqp++;
len--;
if (!parse_eap_params(anqp, eap_len,
&method, &non_eap_inner,
&eap_inner, &credential,
&tunneled_credential))
return false;
if (hs20) {
/*
* TODO: Support EAP-SIM/AKA/AKA' with Hotspot
*/
if (method != EAP_TYPE_TTLS) {
l_debug("EAP method %u not supported", method);
goto next;
}
/* MSCHAPv2 */
if (non_eap_inner != 4) {
l_debug("Non-EAP inner %u not supported",
non_eap_inner);
goto next;
}
/* username/password */
if (credential != 7) {
l_debug("credential type %u not supported",
credential);
goto next;
}
} else {
/* can't use methods without user/password */
if (credential != 7 && tunneled_credential != 7)
goto next;
}
method_out->method = method;
/* nai is guarenteed to NULL terminate and be < 256 bytes */
l_strlcpy(method_out->realm, nai,
sizeof(method_out->realm) - 1);
method_out->non_eap_inner = non_eap_inner;
method_out->eap_inner = eap_inner;
method_out->credential = credential;
method_out->tunneled_credential = tunneled_credential;
return true;
next:
if (len < eap_len)
return false;
anqp += eap_len;
len -= eap_len;
}
return false;
}
/*
* Parses NAI Realm ANQP-element. The code here parses the NAI Realm until an
* acceptable EAP method is found. Once a method is found it is returned via
* method_out. The structure of NAI realm is such that it does not allow for a
* convenient static structure (several nested lists). Since we can only handle
* EAP methods with user/password credentials anyways it makes sense to just
* return the first EAP method found that meets our criteria. In addition, this
* is only being used for Hotspot 2.0, which mandates EAP-TLS/TTLS/SIM/AKA,
* meaning TTLS is the only contender for this parsing.
*
* @param hs20 true if this parsing is for a Hotspot 2.0 network. This will
* restrict what EAP method info is chosen as to comply with the
* Hotspot 2.0 spec (i.e. EAP-TTLS w/ MSCHAPv2 or SIM/AKA/AKA').
*/
bool anqp_parse_nai_realm(const unsigned char *anqp, unsigned int len,
bool hs20, struct anqp_eap_method *method_out)
{
uint16_t count;
if (len < 2)
return false;
count = l_get_le16(anqp);
anqp += 2;
len -= 2;
while (count--) {
uint16_t realm_len;
uint8_t encoding;
uint8_t nai_len;
char nai_realm[256] = { 0 };
/*
* The method list is a variable field, so the only way to
* reliably increment anqp is by realm_len at the very end since
* we dont know how many bytes parse_eap advanced (it does
* internal length checking so it should not overflow). We
* cant incrementally advance anqp/len, hence the hardcoded
* length and pointer adjustments.
*/
if (len < 4)
return false;
realm_len = l_get_le16(anqp);
anqp += 2;
len -= 2;
encoding = anqp[0];
nai_len = anqp[1];
if (len - 2 < nai_len)
return false;
memcpy(nai_realm, anqp + 2, nai_len);
/*
* TODO: Verify NAI encoding in accordance with RFC 4282 ?
*
* The encoding in RFC 4282 seems to only limit which characters
* can be used in an NAI. Since these come in from public
* action frames it could have been spoofed, but ultimately if
* its bogus the AP won't allow us to connect.
*/
if (!util_is_bit_set(encoding, 0))
l_warn("Not verifying NAI encoding");
else if (!l_utf8_validate(nai_realm, nai_len, NULL)) {
l_warn("NAI is not UTF-8");
return false;
}
if (parse_eap(anqp + 2 + nai_len, realm_len - 2 - nai_len,
nai_realm, hs20, method_out))
return true;
if (len < realm_len)
return false;
anqp += realm_len;
len -= realm_len;
}
return false;
}

117
src/anqp.h Normal file
View File

@ -0,0 +1,117 @@
/*
*
* Wireless daemon for Linux
*
* Copyright (C) 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
*
*/
#include <stdbool.h>
#include <stddef.h>
#include <unistd.h>
/* IEEE 802.11-2016 Section 9.4.5 ANQP elements */
enum anqp_element {
/* 0-255 reserved */
ANQP_QUERY_LIST = 256,
ANQP_CAPABILITY_LIST = 257,
ANQP_VENUE_NAME = 258,
ANQP_EMERGENCY_CALL_NUMBER = 259,
ANQP_NETWORK_AUTH_TYPE = 260,
ANQP_ROAMING_CONSORTIUM = 261,
ANQP_IP_ADDRESS_TYPE_AVAILABILITY = 262,
ANQP_NAI_REALM = 263,
ANQP_3GPP_CELLULAR_NETWORK = 264,
ANQP_AP_GEOSPATIAL_LOCATION = 265,
ANQP_AP_CIVIC_LOCATION = 266,
ANQP_AP_LOCATION_PUBLIC_ID = 267,
ANQP_DOMAIN_NAME = 268,
ANQP_EMERGENCY_ALERT_ID_URI = 269,
ANQP_TDLS_CAPABILITY = 270,
ANQP_EMERGENCY_NAI = 271,
ANQP_NEIGHBOR_REPORT = 272,
/* 273-276 reserved */
ANQP_VENUE_URI = 277,
ANQP_ADVICE_OF_CHARGE = 278,
ANQP_LOCAL_CONTENT = 279,
ANQP_NETWORK_AUTH_TYPE_WITH_TIMESTAMP = 280,
/* 281-56796 reserved */
ANQP_VENDOR_SPECIFIC = 56797,
/* 56798-65535 reserved */
};
/* WiFi Alliance Hotspot 2.0 Spec - Section 4 Hotspot 2.0 ANQP-elements */
enum anqp_hs20_element {
ANQP_HS20_QUERY_LIST = 1,
ANQP_HS20_CAPABILITY_LIST = 2,
ANQP_HS20_OPERATOR_FRIENDLY_NAME = 3,
ANQP_HS20_WLAN_METRICS = 4,
ANQP_HS20_CONNECTION_CAPABILITY = 5,
ANQP_HS20_NAI_HOME_REALM_QUERY = 6,
ANQP_HS20_OPERATING_CLASS_INDICATION = 7,
ANQP_HS20_OSU_PROVIDERS_LIST = 8,
/* 9 reserved */
ANQP_HS20_ICON_REQUST = 10,
ANQP_HS20_ICON_BINARY_FILE = 11,
ANQP_HS20_OPERATOR_ICON_METADATA = 12,
ANQP_HS20_OSU_PROVIDERS_NAI_LIST = 13,
/* 14 - 255 reserved */
};
/* IEEE 802.11-2016 Table 9-275 Authentication Parameter types */
enum anqp_auth_parameter_type {
ANQP_AP_EXPANDED_EAP_METHOD = 1,
ANQP_AP_NON_INNER_AUTH_EAP = 2,
ANQP_AP_INNER_AUTH_EAP = 3,
ANQP_AP_EXPANDED_INNER_EAP_METHOD = 4,
ANQP_AP_CREDENTIAL = 5,
ANQP_AP_TUNNELED_EAP_CREDENTIAL = 6,
ANQP_AP_VENDOR_SPECIFIC = 221,
};
struct anqp_iter {
unsigned int max;
unsigned int pos;
const unsigned char *anqp;
unsigned int id;
unsigned int len;
const unsigned char *data;
};
/*
* TODO: Support expanded EAP types
*/
struct anqp_eap_method {
char realm[256];
uint8_t method;
uint8_t non_eap_inner;
uint8_t eap_inner;
uint8_t credential;
uint8_t tunneled_credential;
};
void anqp_iter_init(struct anqp_iter *iter, const unsigned char *anqp,
unsigned int len);
bool anqp_iter_next(struct anqp_iter *iter, uint16_t *id, uint16_t *len,
const void **data);
bool anqp_iter_is_hs20(const struct anqp_iter *iter, uint8_t *stype,
unsigned int *len, const unsigned char **data);
bool anqp_hs20_parse_osu_provider_nai(const unsigned char *anqp,
unsigned int len, const char **nai_out);
bool anqp_parse_nai_realm(const unsigned char *anqp, unsigned int len,
bool hs20, struct anqp_eap_method *method);