Browse Source

Interworking: Parse NAI Realms and match against home realm

Jouni Malinen 13 years ago
parent
commit
73c41a8fab

+ 30 - 0
src/common/ieee802_11_defs.h

@@ -332,6 +332,36 @@ enum anqp_info_id {
 	ANQP_VENDOR_SPECIFIC = 56797
 };
 
+/* NAI Realm list - EAP Method subfield - Authentication Parameter ID */
+enum nai_realm_eap_auth_param {
+	NAI_REALM_EAP_AUTH_EXPANDED_EAP_METHOD = 1,
+	NAI_REALM_EAP_AUTH_NON_EAP_INNER_AUTH = 2,
+	NAI_REALM_EAP_AUTH_INNER_AUTH_EAP_METHOD = 3,
+	NAI_REALM_EAP_AUTH_EXPANDED_INNER_EAP_METHOD = 4,
+	NAI_REALM_EAP_AUTH_CRED_TYPE = 5,
+	NAI_REALM_EAP_AUTH_TUNNELED_CRED_TYPE = 6,
+	NAI_REALM_EAP_AUTH_VENDOR_SPECIFIC = 221
+};
+
+enum nai_realm_eap_auth_inner_non_eap {
+	NAI_REALM_INNER_NON_EAP_PAP = 1,
+	NAI_REALM_INNER_NON_EAP_CHAP = 2,
+	NAI_REALM_INNER_NON_EAP_MSCHAP = 3,
+	NAI_REALM_INNER_NON_EAP_MSCHAPV2 = 4
+};
+
+enum nai_realm_eap_cred_type {
+	NAI_REALM_CRED_TYPE_SIM = 1,
+	NAI_REALM_CRED_TYPE_USIM = 2,
+	NAI_REALM_CRED_TYPE_NFC_SECURE_ELEMENT = 3,
+	NAI_REALM_CRED_TYPE_HARDWARE_TOKEN = 4,
+	NAI_REALM_CRED_TYPE_SOFTOKEN = 5,
+	NAI_REALM_CRED_TYPE_CERTIFICATE = 6,
+	NAI_REALM_CRED_TYPE_USERNAME_PASSWORD = 7,
+	NAI_REALM_CRED_TYPE_NONE = 8,
+	NAI_REALM_CRED_TYPE_ANONYMOUS = 9,
+	NAI_REALM_CRED_TYPE_VENDOR_SPECIFIC = 10
+};
 
 #ifdef _MSC_VER
 #pragma pack(push, 1)

+ 2 - 0
wpa_supplicant/config.c

@@ -1726,6 +1726,7 @@ void wpa_config_free(struct wpa_config *config)
 	os_free(config->config_methods);
 	os_free(config->p2p_ssid_postfix);
 	os_free(config->pssid);
+	os_free(config->home_realm);
 	os_free(config);
 }
 
@@ -2460,6 +2461,7 @@ static const struct global_parse_data global_fields[] = {
 	{ INT_RANGE(filter_ssids, 0, 1), 0 },
 	{ INT(max_num_sta), 0 },
 	{ INT_RANGE(disassoc_low_ack, 0, 1), 0 },
+	{ STR(home_realm), 0 },
 	{ INT_RANGE(interworking, 0, 1), 0 },
 	{ FUNC(hessid), 0 }
 };

+ 5 - 0
wpa_supplicant/config.h

@@ -440,6 +440,11 @@ struct wpa_config {
 	 * Homogeneous ESS. This is used only if interworking is enabled.
 	 */
 	u8 hessid[ETH_ALEN];
+
+	/**
+	 * home_realm - Home Realm for Interworking
+	 */
+	char *home_realm;
 };
 
 

+ 2 - 0
wpa_supplicant/config_file.c

@@ -703,6 +703,8 @@ static void wpa_config_write_global(FILE *f, struct wpa_config *config)
 		fprintf(f, "max_num_sta=%u\n", config->max_num_sta);
 	if (config->disassoc_low_ack)
 		fprintf(f, "disassoc_low_ack=%u\n", config->disassoc_low_ack);
+	if (config->home_realm)
+		fprintf(f, "home_realm=%s\n", config->home_realm);
 	if (config->interworking)
 		fprintf(f, "interworking=%u\n", config->interworking);
 	if (!is_zero_ether_addr(config->hessid))

+ 297 - 2
wpa_supplicant/interworking.c

@@ -19,7 +19,9 @@
 #include "common/gas.h"
 #include "common/wpa_ctrl.h"
 #include "drivers/driver.h"
+#include "eap_common/eap_defs.h"
 #include "wpa_supplicant_i.h"
+#include "config.h"
 #include "bss.h"
 #include "scan.h"
 #include "gas_query.h"
@@ -110,6 +112,265 @@ static int interworking_anqp_send_req(struct wpa_supplicant *wpa_s,
 }
 
 
+struct nai_realm_eap {
+	u8 method;
+	u8 inner_method;
+	enum nai_realm_eap_auth_inner_non_eap inner_non_eap;
+	u8 cred_type;
+	u8 tunneled_cred_type;
+};
+
+struct nai_realm {
+	u8 encoding;
+	char *realm;
+	u8 eap_count;
+	struct nai_realm_eap *eap;
+};
+
+
+static void nai_realm_free(struct nai_realm *realms, u16 count)
+{
+	u16 i;
+
+	if (realms == NULL)
+		return;
+	for (i = 0; i < count; i++) {
+		os_free(realms[i].eap);
+		os_free(realms[i].realm);
+	}
+	os_free(realms);
+}
+
+
+static const u8 * nai_realm_parse_eap(struct nai_realm_eap *e, const u8 *pos,
+				      const u8 *end)
+{
+	u8 elen, auth_count, a;
+	const u8 *e_end;
+
+	if (pos + 3 > end) {
+		wpa_printf(MSG_DEBUG, "No room for EAP Method fixed fields");
+		return NULL;
+	}
+
+	elen = *pos++;
+	if (pos + elen > end || elen < 2) {
+		wpa_printf(MSG_DEBUG, "No room for EAP Method subfield");
+		return NULL;
+	}
+	e_end = pos + elen;
+	e->method = *pos++;
+	auth_count = *pos++;
+	wpa_printf(MSG_DEBUG, "EAP Method: len=%u method=%u auth_count=%u",
+		   elen, e->method, auth_count);
+
+	for (a = 0; a < auth_count; a++) {
+		u8 id, len;
+
+		if (pos + 2 > end || pos + 2 + pos[1] > end) {
+			wpa_printf(MSG_DEBUG, "No room for Authentication "
+				   "Parameter subfield");
+			return NULL;
+		}
+
+		id = *pos++;
+		len = *pos++;
+
+		switch (id) {
+		case NAI_REALM_EAP_AUTH_NON_EAP_INNER_AUTH:
+			if (len < 1)
+				break;
+			e->inner_non_eap = *pos;
+			if (e->method != EAP_TYPE_TTLS)
+				break;
+			switch (*pos) {
+			case NAI_REALM_INNER_NON_EAP_PAP:
+				wpa_printf(MSG_DEBUG, "EAP-TTLS/PAP");
+				break;
+			case NAI_REALM_INNER_NON_EAP_CHAP:
+				wpa_printf(MSG_DEBUG, "EAP-TTLS/CHAP");
+				break;
+			case NAI_REALM_INNER_NON_EAP_MSCHAP:
+				wpa_printf(MSG_DEBUG, "EAP-TTLS/MSCHAP");
+				break;
+			case NAI_REALM_INNER_NON_EAP_MSCHAPV2:
+				wpa_printf(MSG_DEBUG, "EAP-TTLS/MSCHAPV2");
+				break;
+			}
+			break;
+		case NAI_REALM_EAP_AUTH_INNER_AUTH_EAP_METHOD:
+			if (len < 1)
+				break;
+			e->inner_method = *pos;
+			wpa_printf(MSG_DEBUG, "Inner EAP method: %u",
+				   e->inner_method);
+			break;
+		case NAI_REALM_EAP_AUTH_CRED_TYPE:
+			if (len < 1)
+				break;
+			e->cred_type = *pos;
+			wpa_printf(MSG_DEBUG, "Credential Type: %u",
+				   e->cred_type);
+			break;
+		case NAI_REALM_EAP_AUTH_TUNNELED_CRED_TYPE:
+			if (len < 1)
+				break;
+			e->tunneled_cred_type = *pos;
+			wpa_printf(MSG_DEBUG, "Tunneled EAP Method Credential "
+				   "Type: %u", e->tunneled_cred_type);
+			break;
+		default:
+			wpa_printf(MSG_DEBUG, "Unsupported Authentication "
+				   "Parameter: id=%u len=%u", id, len);
+			wpa_hexdump(MSG_DEBUG, "Authentication Parameter "
+				    "Value", pos, len);
+			break;
+		}
+
+		pos += len;
+	}
+
+	return e_end;
+}
+
+
+static const u8 * nai_realm_parse_realm(struct nai_realm *r, const u8 *pos,
+					const u8 *end)
+{
+	u16 len;
+	const u8 *f_end;
+	u8 realm_len, e;
+
+	if (end - pos < 4) {
+		wpa_printf(MSG_DEBUG, "No room for NAI Realm Data "
+			   "fixed fields");
+		return NULL;
+	}
+
+	len = WPA_GET_LE16(pos); /* NAI Realm Data field Length */
+	pos += 2;
+	if (pos + len > end || len < 3) {
+		wpa_printf(MSG_DEBUG, "No room for NAI Realm Data "
+			   "(len=%u; left=%u)",
+			   len, (unsigned int) (end - pos));
+		return NULL;
+	}
+	f_end = pos + len;
+
+	r->encoding = *pos++;
+	realm_len = *pos++;
+	if (pos + realm_len > f_end) {
+		wpa_printf(MSG_DEBUG, "No room for NAI Realm "
+			   "(len=%u; left=%u)",
+			   realm_len, (unsigned int) (f_end - pos));
+		return NULL;
+	}
+	wpa_hexdump_ascii(MSG_DEBUG, "NAI Realm", pos, realm_len);
+	r->realm = os_malloc(realm_len + 1);
+	if (r->realm == NULL)
+		return NULL;
+	os_memcpy(r->realm, pos, realm_len);
+	r->realm[realm_len] = '\0';
+	pos += realm_len;
+
+	if (pos + 1 > f_end) {
+		wpa_printf(MSG_DEBUG, "No room for EAP Method Count");
+		return NULL;
+	}
+	r->eap_count = *pos++;
+	wpa_printf(MSG_DEBUG, "EAP Count: %u", r->eap_count);
+	if (pos + r->eap_count * 3 > f_end) {
+		wpa_printf(MSG_DEBUG, "No room for EAP Methods");
+		return NULL;
+	}
+	r->eap = os_zalloc(r->eap_count * sizeof(struct nai_realm_eap));
+	if (r->eap == NULL)
+		return NULL;
+
+	for (e = 0; e < r->eap_count; e++) {
+		pos = nai_realm_parse_eap(&r->eap[e], pos, f_end);
+		if (pos == NULL)
+			return NULL;
+	}
+
+	return f_end;
+}
+
+
+static struct nai_realm * nai_realm_parse(struct wpabuf *anqp, u16 *count)
+{
+	struct nai_realm *realm;
+	const u8 *pos, *end;
+	u16 i, num;
+
+	if (anqp == NULL || wpabuf_len(anqp) < 2)
+		return NULL;
+
+	pos = wpabuf_head_u8(anqp);
+	end = pos + wpabuf_len(anqp);
+	num = WPA_GET_LE16(pos);
+	wpa_printf(MSG_DEBUG, "NAI Realm Count: %u", num);
+	pos += 2;
+
+	if (num * 5 > end - pos) {
+		wpa_printf(MSG_DEBUG, "Invalid NAI Realm Count %u - not "
+			   "enough data (%u octets) for that many realms",
+			   num, (unsigned int) (end - pos));
+		return NULL;
+	}
+
+	realm = os_zalloc(num * sizeof(struct nai_realm));
+	if (realm == NULL)
+		return NULL;
+
+	for (i = 0; i < num; i++) {
+		pos = nai_realm_parse_realm(&realm[i], pos, end);
+		if (pos == NULL) {
+			nai_realm_free(realm, num);
+			return NULL;
+		}
+	}
+
+	*count = num;
+	return realm;
+}
+
+
+static int nai_realm_match(struct nai_realm *realm, const char *home_realm)
+{
+	char *tmp, *pos, *end;
+	int match = 0;
+
+	if (realm->realm == NULL)
+		return 0;
+
+	if (os_strchr(realm->realm, ';') == NULL)
+		return os_strcasecmp(realm->realm, home_realm) == 0;
+
+	tmp = os_strdup(realm->realm);
+	if (tmp == NULL)
+		return 0;
+
+	pos = tmp;
+	while (*pos) {
+		end = os_strchr(pos, ';');
+		if (end)
+			*end = '\0';
+		if (os_strcasecmp(pos, home_realm) == 0) {
+			match = 1;
+			break;
+		}
+		if (end == NULL)
+			break;
+		pos = end + 1;
+	}
+
+	os_free(tmp);
+
+	return match;
+}
+
+
 int interworking_connect(struct wpa_supplicant *wpa_s, struct wpa_bss *bss)
 {
 	if (bss == NULL)
@@ -122,6 +383,41 @@ int interworking_connect(struct wpa_supplicant *wpa_s, struct wpa_bss *bss)
 }
 
 
+static int interworking_credentials_available(struct wpa_supplicant *wpa_s,
+					      struct wpa_bss *bss)
+{
+	struct nai_realm *realm;
+	u16 count, i;
+	int found = 0;
+
+	if (bss->anqp_nai_realm == NULL)
+		return 0;
+
+	if (wpa_s->conf->home_realm == NULL)
+		return 0;
+
+	wpa_printf(MSG_DEBUG, "Interworking: Parsing NAI Realm list from "
+		   MACSTR, MAC2STR(bss->bssid));
+	realm = nai_realm_parse(bss->anqp_nai_realm, &count);
+	if (realm == NULL) {
+		wpa_printf(MSG_DEBUG, "Interworking: Could not parse NAI "
+			   "Realm list from " MACSTR, MAC2STR(bss->bssid));
+		return 0;
+	}
+
+	for (i = 0; i < count; i++) {
+		if (nai_realm_match(&realm[i], wpa_s->conf->home_realm)) {
+			found++;
+			break;
+		}
+	}
+
+	nai_realm_free(realm, count);
+
+	return found;
+}
+
+
 static void interworking_select_network(struct wpa_supplicant *wpa_s)
 {
 	struct wpa_bss *bss, *selected = NULL;
@@ -130,9 +426,8 @@ static void interworking_select_network(struct wpa_supplicant *wpa_s)
 	wpa_s->network_select = 0;
 
 	dl_list_for_each(bss, &wpa_s->bss, struct wpa_bss, list) {
-		if (bss->anqp_nai_realm == NULL)
+		if (!interworking_credentials_available(wpa_s, bss))
 			continue;
-		/* TODO: verify that matching credentials are available */
 		count++;
 		wpa_msg(wpa_s, MSG_INFO, INTERWORKING_AP MACSTR,
 			MAC2STR(bss->bssid));