HS20: Support credentials with multiple home OIs

Until now Hotspot 2.0 credentials were only supporting one home OI (with
roaming_consortium option) and one required home OI (with
required_roaming_consortium option). To improve the compliance with
Passpoint specification, add the support for multiple home and required
OIs.

The lists of OIs are provided using two new configuration options
home_ois and required_home_ois that expect a list of OIs formatted as
the roaming_consortiums list. It allows to keep the old options to avoid
breaking currently running configurations and better fits the vocabulary
used in the spec.

The OI match algorithm is updated to implement the behavior described in
Passpoint specification v3.2 section 9.1.2 (Home OIs nodes description
PerProviderSubscription/<X+>/HomeSP/HomeOIList/<X+>).

Signed-off-by: Damien Dejean <damiendejean@chromium.org>
This commit is contained in:
Damien Dejean 2022-09-15 08:02:13 +00:00 committed by Jouni Malinen
parent 0143dc1cb6
commit 58eb905ad3
6 changed files with 282 additions and 105 deletions

View file

@ -199,7 +199,26 @@ Credentials can be pre-configured for automatic network selection:
# be used to configure alternative FQDNs that will be considered home
# networks.
#
# home_ois: Home OI(s)
# This string field contains one or more comma delimited OIs (hexdump)
# identifying the access the access points that support authentication
# with this credential. There are an alternative to the use of the realm
# parameter. When using Home OIs to match the network, the EAP parameters
# need to be pre-configured with the credentials since the NAI Realm
# information may not be available or fetched.
# A successful authentication with the access point is possible as soon
# as at least one Home OI from the list matches an OI in the Roaming
# Consortium advertised by the access point.
# (Hotspot 2.0 PerProviderSubscription/<X+>/HomeSP/HomeOIList/<X+>/HomeOI)
#
# required_home_ois: Required Home OI(s)
# This string field contains the set of Home OI(s) (hexdump) that are
# required to be advertised by the AP for the credential to be considered
# matching.
# (Hotspot 2.0 PerProviderSubscription/<X+>/HomeSP/HomeOIList/<X+>/HomeOIRequired)
#
# roaming_consortium: Roaming Consortium OI
# Deprecated: use home_ois instead.
# If roaming_consortium_len is non-zero, this field contains the
# Roaming Consortium OI that can be used to determine which access
# points support authentication with this credential. This is an
@ -209,6 +228,7 @@ Credentials can be pre-configured for automatic network selection:
# may not be available or fetched.
#
# required_roaming_consortium: Required Roaming Consortium OI
# Deprecated: use required_home_ois instead.
# If required_roaming_consortium_len is non-zero, this field contains the
# Roaming Consortium OI that is required to be advertised by the AP for
# the credential to be considered matching.
@ -325,7 +345,7 @@ Credentials can be pre-configured for automatic network selection:
# password="password"
# ca_cert="/etc/wpa_supplicant/ca.pem"
# domain="example.com"
# roaming_consortium=223344
# home_ois="223344"
# roaming_consortiums="112233,4455667788,aabbcc"
# eap=TTLS
# phase2="auth=MSCHAPV2"

View file

@ -3532,53 +3532,62 @@ static int wpa_config_set_cred_req_conn_capab(struct wpa_cred *cred,
}
static int wpa_config_set_cred_roaming_consortiums(struct wpa_cred *cred,
static int
wpa_config_set_cred_ois(u8 cred_ois[MAX_ROAMING_CONS][MAX_ROAMING_CONS_OI_LEN],
size_t cred_ois_len[MAX_ROAMING_CONS],
unsigned int *cred_num_ois,
const char *value)
{
u8 roaming_consortiums[MAX_ROAMING_CONS][MAX_ROAMING_CONS_OI_LEN];
size_t roaming_consortiums_len[MAX_ROAMING_CONS];
unsigned int num_roaming_consortiums = 0;
u8 ois[MAX_ROAMING_CONS][MAX_ROAMING_CONS_OI_LEN];
size_t ois_len[MAX_ROAMING_CONS];
unsigned int num_ois = 0;
const char *pos, *end;
size_t len;
os_memset(roaming_consortiums, 0, sizeof(roaming_consortiums));
os_memset(roaming_consortiums_len, 0, sizeof(roaming_consortiums_len));
len = os_strlen(value);
if (len / 2 < 3) {
wpa_printf(MSG_ERROR,
"Invalid organisation identifier (OI) list: %s",
value);
return -1;
}
os_memset(ois, 0, sizeof(ois));
os_memset(ois_len, 0, sizeof(ois_len));
for (pos = value;;) {
end = os_strchr(pos, ',');
len = end ? (size_t) (end - pos) : os_strlen(pos);
if (!end && len == 0)
break;
if (len == 0 || (len & 1) != 0 ||
if (len / 2 < 3 || (len & 1) != 0 ||
len / 2 > MAX_ROAMING_CONS_OI_LEN ||
hexstr2bin(pos,
roaming_consortiums[num_roaming_consortiums],
ois[num_ois],
len / 2) < 0) {
wpa_printf(MSG_INFO,
"Invalid roaming_consortiums entry: %s",
"Invalid organisation identifier (OI) entry: %s",
pos);
return -1;
}
roaming_consortiums_len[num_roaming_consortiums] = len / 2;
num_roaming_consortiums++;
ois_len[num_ois] = len / 2;
num_ois++;
if (!end)
break;
if (num_roaming_consortiums >= MAX_ROAMING_CONS) {
if (num_ois >= MAX_ROAMING_CONS) {
wpa_printf(MSG_INFO,
"Too many roaming_consortiums OIs");
"Too many OIs");
return -1;
}
pos = end + 1;
}
os_memcpy(cred->roaming_consortiums, roaming_consortiums,
sizeof(roaming_consortiums));
os_memcpy(cred->roaming_consortiums_len, roaming_consortiums_len,
sizeof(roaming_consortiums_len));
cred->num_roaming_consortiums = num_roaming_consortiums;
os_memcpy(cred_ois, ois, sizeof(ois));
os_memcpy(cred_ois_len, ois_len, sizeof(ois_len));
*cred_num_ois = num_ois;
return 0;
}
@ -3813,36 +3822,70 @@ int wpa_config_set_cred(struct wpa_cred *cred, const char *var,
}
if (os_strcmp(var, "roaming_consortium") == 0) {
if (len < 3 || len > sizeof(cred->roaming_consortium)) {
if (len < 3 || len > sizeof(cred->home_ois[0])) {
wpa_printf(MSG_ERROR, "Line %d: invalid "
"roaming_consortium length %d (3..15 "
"expected)", line, (int) len);
os_free(val);
return -1;
}
os_memcpy(cred->roaming_consortium, val, len);
cred->roaming_consortium_len = len;
wpa_printf(MSG_WARNING,
"Line %d: option roaming_consortium is deprecated and will be removed in the future",
line);
os_memcpy(cred->home_ois[0], val, len);
cred->home_ois_len[0] = len;
cred->num_home_ois = 1;
os_free(val);
return 0;
}
if (os_strcmp(var, "required_roaming_consortium") == 0) {
if (len < 3 || len > sizeof(cred->required_roaming_consortium))
{
if (len < 3 || len > sizeof(cred->required_home_ois[0])) {
wpa_printf(MSG_ERROR, "Line %d: invalid "
"required_roaming_consortium length %d "
"(3..15 expected)", line, (int) len);
os_free(val);
return -1;
}
os_memcpy(cred->required_roaming_consortium, val, len);
cred->required_roaming_consortium_len = len;
wpa_printf(MSG_WARNING,
"Line %d: option required_roaming_consortium is deprecated and will be removed in the future",
line);
os_memcpy(cred->required_home_ois[0], val, len);
cred->required_home_ois_len[0] = len;
cred->num_required_home_ois = 1;
os_free(val);
return 0;
}
if (os_strcmp(var, "home_ois") == 0) {
res = wpa_config_set_cred_ois(cred->home_ois,
cred->home_ois_len,
&cred->num_home_ois,
val);
if (res < 0)
wpa_printf(MSG_ERROR, "Line %d: invalid home_ois",
line);
os_free(val);
return res;
}
if (os_strcmp(var, "required_home_ois") == 0) {
res = wpa_config_set_cred_ois(cred->required_home_ois,
cred->required_home_ois_len,
&cred->num_required_home_ois,
val);
if (res < 0)
wpa_printf(MSG_ERROR,
"Line %d: invalid required_home_ois", line);
os_free(val);
return res;
}
if (os_strcmp(var, "roaming_consortiums") == 0) {
res = wpa_config_set_cred_roaming_consortiums(cred, val);
res = wpa_config_set_cred_ois(cred->roaming_consortiums,
cred->roaming_consortiums_len,
&cred->num_roaming_consortiums,
val);
if (res < 0)
wpa_printf(MSG_ERROR,
"Line %d: invalid roaming_consortiums",
@ -4154,14 +4197,14 @@ char * wpa_config_get_cred_no_key(struct wpa_cred *cred, const char *var)
size_t buflen;
char *buf;
if (!cred->roaming_consortium_len)
if (!cred->num_home_ois || !cred->home_ois_len[0])
return NULL;
buflen = cred->roaming_consortium_len * 2 + 1;
buflen = cred->home_ois_len[0] * 2 + 1;
buf = os_malloc(buflen);
if (buf == NULL)
return NULL;
wpa_snprintf_hex(buf, buflen, cred->roaming_consortium,
cred->roaming_consortium_len);
wpa_snprintf_hex(buf, buflen, cred->home_ois[0],
cred->home_ois_len[0]);
return buf;
}
@ -4169,14 +4212,64 @@ char * wpa_config_get_cred_no_key(struct wpa_cred *cred, const char *var)
size_t buflen;
char *buf;
if (!cred->required_roaming_consortium_len)
if (!cred->num_required_home_ois ||
!cred->required_home_ois_len[0])
return NULL;
buflen = cred->required_roaming_consortium_len * 2 + 1;
buflen = cred->required_home_ois_len[0] * 2 + 1;
buf = os_malloc(buflen);
if (buf == NULL)
return NULL;
wpa_snprintf_hex(buf, buflen, cred->required_roaming_consortium,
cred->required_roaming_consortium_len);
wpa_snprintf_hex(buf, buflen, cred->required_home_ois[0],
cred->required_home_ois_len[0]);
return buf;
}
if (os_strcmp(var, "home_ois") == 0) {
size_t buflen;
char *buf, *pos;
size_t i;
if (!cred->num_home_ois)
return NULL;
buflen = cred->num_home_ois * MAX_ROAMING_CONS_OI_LEN * 2 + 1;
buf = os_malloc(buflen);
if (!buf)
return NULL;
pos = buf;
for (i = 0; i < cred->num_home_ois; i++) {
if (i > 0)
*pos++ = ',';
pos += wpa_snprintf_hex(
pos, buf + buflen - pos,
cred->home_ois[i],
cred->home_ois_len[i]);
}
*pos = '\0';
return buf;
}
if (os_strcmp(var, "required_home_ois") == 0) {
size_t buflen;
char *buf, *pos;
size_t i;
if (!cred->num_required_home_ois)
return NULL;
buflen = cred->num_required_home_ois *
MAX_ROAMING_CONS_OI_LEN * 2 + 1;
buf = os_malloc(buflen);
if (!buf)
return NULL;
pos = buf;
for (i = 0; i < cred->num_required_home_ois; i++) {
if (i > 0)
*pos++ = ',';
pos += wpa_snprintf_hex(
pos, buf + buflen - pos,
cred->required_home_ois[i],
cred->required_home_ois_len[i]);
}
*pos = '\0';
return buf;
}

View file

@ -259,36 +259,50 @@ struct wpa_cred {
size_t num_domain;
/**
* roaming_consortium - Roaming Consortium OI
* home_ois - Home OIs
*
* If roaming_consortium_len is non-zero, this field contains the
* Roaming Consortium OI that can be used to determine which access
* points support authentication with this credential. This is an
* alternative to the use of the realm parameter. When using Roaming
* Consortium to match the network, the EAP parameters need to be
* pre-configured with the credential since the NAI Realm information
* may not be available or fetched.
* If num_home_ois is non-zero, this field contains the set of Home OIs
* that can be use to determine which access points support
* authentication with this credential. These are an alternative to the
* use of the realm parameter. When using Home OIs to match the network,
* the EAP parameters need to be pre-configured with the credentials
* since the NAI Realm information may not be available or fetched.
* A successful authentication with the access point is possible as soon
* as at least one Home OI from the list matches an OI in the Roaming
* Consortium list advertised by the access point.
* (Hotspot 2.0 PerProviderSubscription/<X+>/HomeSP/HomeOIList/<X+>/HomeOI)
*/
u8 roaming_consortium[15];
u8 home_ois[MAX_ROAMING_CONS][MAX_ROAMING_CONS_OI_LEN];
/**
* roaming_consortium_len - Length of roaming_consortium
* home_ois_len - Length of home_ois[i]
*/
size_t roaming_consortium_len;
size_t home_ois_len[MAX_ROAMING_CONS];
/**
* required_roaming_consortium - Required Roaming Consortium OI
* num_home_ois - Number of entries in home_ois
*/
unsigned int num_home_ois;
/**
* required_home_ois - Required Home OI(s)
*
* If required_roaming_consortium_len is non-zero, this field contains
* the Roaming Consortium OI that is required to be advertised by the AP
* for the credential to be considered matching.
* If required_home_ois_len is non-zero, this field contains the set of
* Home OI(s) that are required to be advertised by the AP for the
* credential to be considered matching.
* (Hotspot 2.0 PerProviderSubscription/<X+>/HomeSP/HomeOIList/<X+>/HomeOIRequired)
*/
u8 required_roaming_consortium[15];
u8 required_home_ois[MAX_ROAMING_CONS][MAX_ROAMING_CONS_OI_LEN];
/**
* required_roaming_consortium_len - Length of required_roaming_consortium
* required_home_ois_len - Length of required_home_ois
*/
size_t required_roaming_consortium_len;
size_t required_home_ois_len[MAX_ROAMING_CONS];
/**
* num_required_home_ois - Number of entries in required_home_ois
*/
unsigned int num_required_home_ois;
/**
* roaming_consortiums - Roaming Consortium OI(s) memberships

View file

@ -922,12 +922,6 @@ static void wpa_config_write_cred(FILE *f, struct wpa_cred *cred)
if (cred->domain_suffix_match)
fprintf(f, "\tdomain_suffix_match=\"%s\"\n",
cred->domain_suffix_match);
if (cred->roaming_consortium_len) {
fprintf(f, "\troaming_consortium=");
for (i = 0; i < cred->roaming_consortium_len; i++)
fprintf(f, "%02x", cred->roaming_consortium[i]);
fprintf(f, "\n");
}
if (cred->eap_method) {
const char *name;
name = eap_get_name(cred->eap_method[0].vendor,
@ -1003,12 +997,32 @@ static void wpa_config_write_cred(FILE *f, struct wpa_cred *cred)
}
}
if (cred->required_roaming_consortium_len) {
fprintf(f, "\trequired_roaming_consortium=");
for (i = 0; i < cred->required_roaming_consortium_len; i++)
if (cred->num_home_ois) {
size_t j;
fprintf(f, "\thome_ois=\"");
for (i = 0; i < cred->num_home_ois; i++) {
if (i > 0)
fprintf(f, ",");
for (j = 0; j < cred->home_ois_len[i]; j++)
fprintf(f, "%02x",
cred->required_roaming_consortium[i]);
fprintf(f, "\n");
cred->home_ois[i][j]);
}
fprintf(f, "\"\n");
}
if (cred->num_required_home_ois) {
size_t j;
fprintf(f, "\trequired_home_ois=\"");
for (i = 0; i < cred->num_required_home_ois; i++) {
if (i > 0)
fprintf(f, ",");
for (j = 0; j < cred->required_home_ois_len[i]; j++)
fprintf(f, "%02x",
cred->required_home_ois[i][j]);
}
fprintf(f, "\"\n");
}
if (cred->num_roaming_consortiums) {

View file

@ -144,9 +144,9 @@ static int cred_with_roaming_consortium(struct wpa_supplicant *wpa_s)
struct wpa_cred *cred;
for (cred = wpa_s->conf->cred; cred; cred = cred->next) {
if (cred->roaming_consortium_len)
if (cred->num_home_ois)
return 1;
if (cred->required_roaming_consortium_len)
if (cred->num_required_home_ois)
return 1;
if (cred->num_roaming_consortiums)
return 1;
@ -1097,8 +1097,7 @@ fail:
}
static int roaming_consortium_element_match(const u8 *ie, const u8 *rc_id,
size_t rc_len)
static int oi_element_match(const u8 *ie, const u8 *oi, size_t oi_len)
{
const u8 *pos, *end;
u8 lens;
@ -1123,24 +1122,24 @@ static int roaming_consortium_element_match(const u8 *ie, const u8 *rc_id,
if ((lens & 0x0f) + (lens >> 4) > end - pos)
return 0;
if ((lens & 0x0f) == rc_len && os_memcmp(pos, rc_id, rc_len) == 0)
if ((lens & 0x0f) == oi_len && os_memcmp(pos, oi, oi_len) == 0)
return 1;
pos += lens & 0x0f;
if ((lens >> 4) == rc_len && os_memcmp(pos, rc_id, rc_len) == 0)
if ((lens >> 4) == oi_len && os_memcmp(pos, oi, oi_len) == 0)
return 1;
pos += lens >> 4;
if (pos < end && (size_t) (end - pos) == rc_len &&
os_memcmp(pos, rc_id, rc_len) == 0)
if (pos < end && (size_t) (end - pos) == oi_len &&
os_memcmp(pos, oi, oi_len) == 0)
return 1;
return 0;
}
static int roaming_consortium_anqp_match(const struct wpabuf *anqp,
const u8 *rc_id, size_t rc_len)
static int oi_anqp_match(const struct wpabuf *anqp, const u8 *oi,
size_t oi_len)
{
const u8 *pos, *end;
u8 len;
@ -1156,7 +1155,7 @@ static int roaming_consortium_anqp_match(const struct wpabuf *anqp,
len = *pos++;
if (len > end - pos)
break;
if (len == rc_len && os_memcmp(pos, rc_id, rc_len) == 0)
if (len == oi_len && os_memcmp(pos, oi, oi_len) == 0)
return 1;
pos += len;
}
@ -1165,11 +1164,26 @@ static int roaming_consortium_anqp_match(const struct wpabuf *anqp,
}
static int roaming_consortium_match(const u8 *ie, const struct wpabuf *anqp,
const u8 *rc_id, size_t rc_len)
static int oi_match(const u8 *ie, const struct wpabuf *anqp,
const u8 *oi, size_t oi_len)
{
return roaming_consortium_element_match(ie, rc_id, rc_len) ||
roaming_consortium_anqp_match(anqp, rc_id, rc_len);
return oi_element_match(ie, oi, oi_len) ||
oi_anqp_match(anqp, oi, oi_len);
}
static int cred_home_ois_match(const u8 *ie, const struct wpabuf *anqp,
const struct wpa_cred *cred) {
unsigned int i;
/* There's a match if at least one of the home OI matches. */
for (i = 0; i < cred->num_home_ois; i++) {
if (oi_match(ie, anqp, cred->home_ois[i],
cred->home_ois_len[i]))
return 1;
}
return 0;
}
@ -1180,8 +1194,7 @@ static int cred_roaming_consortiums_match(const u8 *ie,
unsigned int i;
for (i = 0; i < cred->num_roaming_consortiums; i++) {
if (roaming_consortium_match(ie, anqp,
cred->roaming_consortiums[i],
if (oi_match(ie, anqp, cred->roaming_consortiums[i],
cred->roaming_consortiums_len[i]))
return 1;
}
@ -1193,8 +1206,9 @@ static int cred_roaming_consortiums_match(const u8 *ie,
static int cred_no_required_oi_match(struct wpa_cred *cred, struct wpa_bss *bss)
{
const u8 *ie;
unsigned int i;
if (cred->required_roaming_consortium_len == 0)
if (cred->num_required_home_ois == 0)
return 0;
ie = wpa_bss_get_ie(bss, WLAN_EID_ROAMING_CONSORTIUM);
@ -1203,11 +1217,16 @@ static int cred_no_required_oi_match(struct wpa_cred *cred, struct wpa_bss *bss)
(bss->anqp == NULL || bss->anqp->roaming_consortium == NULL))
return 1;
return !roaming_consortium_match(ie,
bss->anqp ?
/* According to Passpoint specification, there must be a match for
* each required home OI provided. */
for (i = 0; i < cred->num_required_home_ois; i++) {
if (!oi_match(ie, bss->anqp ?
bss->anqp->roaming_consortium : NULL,
cred->required_roaming_consortium,
cred->required_roaming_consortium_len);
cred->required_home_ois[i],
cred->required_home_ois_len[i]))
return 1;
}
return 0;
}
@ -1407,26 +1426,24 @@ static struct wpa_cred * interworking_credentials_available_roaming_consortium(
return NULL;
for (cred = wpa_s->conf->cred; cred; cred = cred->next) {
if (cred->roaming_consortium_len == 0 &&
if (cred->num_home_ois == 0 &&
cred->num_required_home_ois == 0 &&
cred->num_roaming_consortiums == 0)
continue;
if (!cred->eap_method)
continue;
if ((cred->roaming_consortium_len == 0 ||
!roaming_consortium_match(ie, anqp,
cred->roaming_consortium,
cred->roaming_consortium_len)) &&
!cred_roaming_consortiums_match(ie, anqp, cred) &&
(cred->required_roaming_consortium_len == 0 ||
!roaming_consortium_match(
ie, anqp, cred->required_roaming_consortium,
cred->required_roaming_consortium_len)))
/* If there's required home OIs, there must be a match for each
* required OI (see Passpoint v3.2 - 9.1.2 - RequiredHomeOI). */
if (cred->num_required_home_ois > 0 &&
cred_no_required_oi_match(cred, bss))
continue;
if (cred_no_required_oi_match(cred, bss))
if (!cred_home_ois_match(ie, anqp, cred) &&
!cred_roaming_consortiums_match(ie, anqp, cred))
continue;
if (!ignore_bw && cred_below_min_backhaul(wpa_s, cred, bss))
continue;
if (!ignore_bw && cred_over_max_bss_load(wpa_s, cred, bss))
@ -1641,8 +1658,7 @@ static int interworking_connect_roaming_consortium(
ie = wpa_bss_get_ie(bss, WLAN_EID_ROAMING_CONSORTIUM);
anqp = bss->anqp ? bss->anqp->roaming_consortium : NULL;
for (i = 0; (ie || anqp) && i < cred->num_roaming_consortiums; i++) {
if (!roaming_consortium_match(
ie, anqp, cred->roaming_consortiums[i],
if (!oi_match(ie, anqp, cred->roaming_consortiums[i],
cred->roaming_consortiums_len[i]))
continue;

View file

@ -683,7 +683,26 @@ fast_reauth=1
# be used to configure alternative FQDNs that will be considered home
# networks.
#
# home_ois: Home OI(s)
# This string field contains one or more comma delimited OIs (hexdump)
# identifying the access the access points that support authentication
# with this credential. There are an alternative to the use of the realm
# parameter. When using Home OIs to match the network, the EAP parameters
# need to be pre-configured with the credentials since the NAI Realm
# information may not be available or fetched.
# A successful authentication with the access point is possible as soon
# as at least one Home OI from the list matches an OI in the Roaming
# Consortium advertised by the access point.
# (Hotspot 2.0 PerProviderSubscription/<X+>/HomeSP/HomeOIList/<X+>/HomeOI)
#
# required_home_ois: Required Home OI(s)
# This string field contains the set of Home OI(s) (hexdump) that are
# required to be advertised by the AP for the credential to be considered
# matching.
# (Hotspot 2.0 PerProviderSubscription/<X+>/HomeSP/HomeOIList/<X+>/HomeOIRequired)
#
# roaming_consortium: Roaming Consortium OI
# Deprecated: use home_ois instead.
# If roaming_consortium_len is non-zero, this field contains the
# Roaming Consortium OI that can be used to determine which access
# points support authentication with this credential. This is an
@ -693,6 +712,7 @@ fast_reauth=1
# may not be available or fetched.
#
# required_roaming_consortium: Required Roaming Consortium OI
# Deprecated: use required_home_ois instead.
# If required_roaming_consortium_len is non-zero, this field contains the
# Roaming Consortium OI that is required to be advertised by the AP for
# the credential to be considered matching.
@ -798,7 +818,7 @@ fast_reauth=1
# password="password"
# ca_cert="/etc/wpa_supplicant/ca.pem"
# domain="example.com"
# roaming_consortium=223344
# home_ois="223344"
# eap=TTLS
# phase2="auth=MSCHAPV2"
#}