Interworking: Parse NAI Realms and match against home realm
authorJouni Malinen <jouni@qca.qualcomm.com>
Wed, 5 Oct 2011 11:38:13 +0000 (14:38 +0300)
committerJouni Malinen <j@w1.fi>
Sun, 16 Oct 2011 20:55:34 +0000 (23:55 +0300)
src/common/ieee802_11_defs.h
wpa_supplicant/config.c
wpa_supplicant/config.h
wpa_supplicant/config_file.c
wpa_supplicant/interworking.c

index 6a75cee..056e90f 100644 (file)
@@ -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)
index 31ce26f..507f6d4 100644 (file)
@@ -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 }
 };
index 300cf28..de14732 100644 (file)
@@ -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;
 };
 
 
index a5d0843..ba14da1 100644 (file)
@@ -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))
index 35a3a2b..5a5ebae 100644 (file)
@@ -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));