diff --git a/wpa_supplicant/config.c b/wpa_supplicant/config.c index 6811244a1..ff054c2d7 100644 --- a/wpa_supplicant/config.c +++ b/wpa_supplicant/config.c @@ -1730,6 +1730,8 @@ void wpa_config_free(struct wpa_config *config) os_free(config->home_username); os_free(config->home_password); os_free(config->home_ca_cert); + os_free(config->home_imsi); + os_free(config->home_milenage); os_free(config); } @@ -2489,6 +2491,8 @@ static const struct global_parse_data global_fields[] = { { STR(home_username), 0 }, { STR(home_password), 0 }, { STR(home_ca_cert), 0 }, + { STR(home_imsi), 0 }, + { STR(home_milenage), 0 }, { INT_RANGE(interworking, 0, 1), 0 }, { FUNC(hessid), 0 } }; diff --git a/wpa_supplicant/config.h b/wpa_supplicant/config.h index 62cac75ac..3741b9b71 100644 --- a/wpa_supplicant/config.h +++ b/wpa_supplicant/config.h @@ -460,6 +460,17 @@ struct wpa_config { * home_ca_cert - CA certificate for Interworking network selection */ char *home_ca_cert; + + /** + * home_imsi - IMSI in | | '-' | format + */ + char *home_imsi; + + /** + * home_milenage - Milenage parameters for SIM/USIM simulator in + * :: format + */ + char *home_milenage; }; diff --git a/wpa_supplicant/config_file.c b/wpa_supplicant/config_file.c index ba546a2c9..6c3aa9c37 100644 --- a/wpa_supplicant/config_file.c +++ b/wpa_supplicant/config_file.c @@ -712,6 +712,10 @@ static void wpa_config_write_global(FILE *f, struct wpa_config *config) fprintf(f, "home_password=%s\n", config->home_password); if (config->home_ca_cert) fprintf(f, "home_ca_cert=%s\n", config->home_ca_cert); + if (config->home_imsi) + fprintf(f, "home_imsi=%s\n", config->home_imsi); + if (config->home_milenage) + fprintf(f, "home_milenage=%s\n", config->home_milenage); if (config->interworking) fprintf(f, "interworking=%u\n", config->interworking); if (!is_zero_ether_addr(config->hessid)) diff --git a/wpa_supplicant/interworking.c b/wpa_supplicant/interworking.c index e7fcd8365..545c951e6 100644 --- a/wpa_supplicant/interworking.c +++ b/wpa_supplicant/interworking.c @@ -30,6 +30,18 @@ #include "interworking.h" +#if defined(EAP_SIM) | defined(EAP_SIM_DYNAMIC) +#define INTERWORKING_3GPP +#else +#if defined(EAP_AKA) | defined(EAP_AKA_DYNAMIC) +#define INTERWORKING_3GPP +#else +#if defined(EAP_AKA_PRIME) | defined(EAP_AKA_PRIME_DYNAMIC) +#define INTERWORKING_3GPP +#endif +#endif +#endif + static void interworking_next_anqp_fetch(struct wpa_supplicant *wpa_s); @@ -343,7 +355,7 @@ static int nai_realm_match(struct nai_realm *realm, const char *home_realm) char *tmp, *pos, *end; int match = 0; - if (realm->realm == NULL) + if (realm->realm == NULL || home_realm == NULL) return 0; if (os_strchr(realm->realm, ';') == NULL) @@ -431,6 +443,187 @@ struct nai_realm_eap * nai_realm_find_eap(struct wpa_supplicant *wpa_s, } +#ifdef INTERWORKING_3GPP + +static int plmn_id_match(struct wpabuf *anqp, const char *imsi) +{ + const char *sep; + u8 plmn[3]; + const u8 *pos, *end; + u8 udhl; + + sep = os_strchr(imsi, '-'); + if (sep == NULL || (sep - imsi != 5 && sep - imsi != 6)) + return 0; + + /* See Annex A of 3GPP TS 24.234 v8.1.0 for description */ + plmn[0] = (imsi[0] - '0') | ((imsi[1] - '0') << 4); + plmn[1] = imsi[2] - '0'; + if (sep - imsi == 6) + plmn[1] |= (imsi[5] - '0') << 4; + else + plmn[1] |= 0xf0; + plmn[2] = (imsi[3] - '0') | ((imsi[4] - '0') << 4); + + if (anqp == NULL) + return 0; + pos = wpabuf_head_u8(anqp); + end = pos + wpabuf_len(anqp); + if (pos + 2 > end) + return 0; + if (*pos != 0) { + wpa_printf(MSG_DEBUG, "Unsupported GUD version 0x%x", *pos); + return 0; + } + pos++; + udhl = *pos++; + if (pos + udhl > end) { + wpa_printf(MSG_DEBUG, "Invalid UDHL"); + return 0; + } + end = pos + udhl; + + while (pos + 2 <= end) { + u8 iei, len; + const u8 *l_end; + iei = *pos++; + len = *pos++ & 0x7f; + if (pos + len > end) + break; + l_end = pos + len; + + if (iei == 0 && len > 0) { + /* PLMN List */ + u8 num, i; + num = *pos++; + for (i = 0; i < num; i++) { + if (pos + 3 > end) + break; + if (os_memcmp(pos, plmn, 3) == 0) + return 1; /* Found matching PLMN */ + } + } + + pos = l_end; + } + + return 0; +} + + +static int set_root_nai(struct wpa_ssid *ssid, const char *imsi, char prefix) +{ + const char *sep, *msin; + char nai[100], *end, *pos; + size_t msin_len, plmn_len; + + /* + * TS 23.003, Clause 14 (3GPP to WLAN Interworking) + * Root NAI: + * @wlan.mnc.mcc.3gppnetwork.org + * is zero-padded to three digits in case two-digit MNC is used + */ + + if (imsi == NULL || os_strlen(imsi) > 16) { + wpa_printf(MSG_DEBUG, "No valid IMSI available"); + return -1; + } + sep = os_strchr(imsi, '-'); + if (sep == NULL) + return -1; + plmn_len = sep - imsi; + if (plmn_len != 5 && plmn_len != 6) + return -1; + msin = sep + 1; + msin_len = os_strlen(msin); + + pos = nai; + end = pos + sizeof(nai); + *pos++ = prefix; + os_memcpy(pos, imsi, plmn_len); + pos += plmn_len; + os_memcpy(pos, msin, msin_len); + pos += msin_len; + pos += os_snprintf(pos, end - pos, "@wlan.mnc"); + if (plmn_len == 5) { + *pos++ = '0'; + *pos++ = imsi[3]; + *pos++ = imsi[4]; + } else { + *pos++ = imsi[3]; + *pos++ = imsi[4]; + *pos++ = imsi[5]; + } + pos += os_snprintf(pos, end - pos, ".mcc%c%c%c.3gppnetwork.org", + imsi[0], imsi[1], imsi[2]); + + return wpa_config_set_quoted(ssid, "identity", nai); +} + +#endif /* INTERWORKING_3GPP */ + + +static int interworking_connect_3gpp(struct wpa_supplicant *wpa_s, + struct wpa_bss *bss) +{ +#ifdef INTERWORKING_3GPP + struct wpa_ssid *ssid; + const u8 *ie; + + ie = wpa_bss_get_ie(bss, WLAN_EID_SSID); + wpa_printf(MSG_DEBUG, "Interworking: Connect with " MACSTR " (3GPP)", + MAC2STR(bss->bssid)); + + ssid = wpa_config_add_network(wpa_s->conf); + if (ssid == NULL) + return -1; + + wpas_notify_network_added(wpa_s, ssid); + wpa_config_set_network_defaults(ssid); + ssid->temporary = 1; + ssid->ssid = os_zalloc(ie[1] + 1); + if (ssid->ssid == NULL) + goto fail; + os_memcpy(ssid->ssid, ie + 2, ie[1]); + ssid->ssid_len = ie[1]; + + /* TODO: figure out whether to use EAP-SIM, EAP-AKA, or EAP-AKA' */ + if (wpa_config_set(ssid, "eap", "SIM", 0) < 0) { + wpa_printf(MSG_DEBUG, "EAP-SIM not supported"); + goto fail; + } + if (set_root_nai(ssid, wpa_s->conf->home_imsi, '1') < 0) { + wpa_printf(MSG_DEBUG, "Failed to set Root NAI"); + goto fail; + } + + if (wpa_s->conf->home_milenage && wpa_s->conf->home_milenage[0]) { + if (wpa_config_set_quoted(ssid, "password", + wpa_s->conf->home_milenage) < 0) + goto fail; + } else { + /* TODO: PIN */ + if (wpa_config_set_quoted(ssid, "pcsc", "") < 0) + goto fail; + } + + if (wpa_s->conf->home_password && wpa_s->conf->home_password[0] && + wpa_config_set_quoted(ssid, "password", wpa_s->conf->home_password) + < 0) + goto fail; + + wpa_supplicant_select_network(wpa_s, ssid); + + return 0; + +fail: + wpas_notify_network_removed(wpa_s, ssid); + wpa_config_remove_network(wpa_s->conf, ssid->id); +#endif /* INTERWORKING_3GPP */ + return -1; +} + + int interworking_connect(struct wpa_supplicant *wpa_s, struct wpa_bss *bss) { struct wpa_ssid *ssid; @@ -453,8 +646,7 @@ int interworking_connect(struct wpa_supplicant *wpa_s, struct wpa_bss *bss) if (realm == NULL) { wpa_printf(MSG_DEBUG, "Interworking: Could not parse NAI " "Realm list from " MACSTR, MAC2STR(bss->bssid)); - nai_realm_free(realm, count); - return -1; + count = 0; } for (i = 0; i < count; i++) { @@ -466,6 +658,12 @@ int interworking_connect(struct wpa_supplicant *wpa_s, struct wpa_bss *bss) } if (!eap) { + if (interworking_connect_3gpp(wpa_s, bss) == 0) { + if (realm) + nai_realm_free(realm, count); + return 0; + } + wpa_printf(MSG_DEBUG, "Interworking: No matching credentials " "and EAP method found for " MACSTR, MAC2STR(bss->bssid)); @@ -564,8 +762,31 @@ fail: } -static int interworking_credentials_available(struct wpa_supplicant *wpa_s, - struct wpa_bss *bss) +static int interworking_credentials_available_3gpp( + struct wpa_supplicant *wpa_s, struct wpa_bss *bss) +{ + int ret = 0; + +#ifdef INTERWORKING_3GPP + if (bss->anqp_3gpp == NULL) + return ret; + + if (wpa_s->conf->home_imsi == NULL || !wpa_s->conf->home_imsi[0] || + wpa_s->conf->home_milenage == NULL || + !wpa_s->conf->home_milenage[0]) + return ret; + + wpa_printf(MSG_DEBUG, "Interworking: Parsing 3GPP info from " MACSTR, + MAC2STR(bss->bssid)); + ret = plmn_id_match(bss->anqp_3gpp, wpa_s->conf->home_imsi); + wpa_printf(MSG_DEBUG, "PLMN match %sfound", ret ? "" : "not "); +#endif /* INTERWORKING_3GPP */ + return ret; +} + + +static int interworking_credentials_available_realm( + struct wpa_supplicant *wpa_s, struct wpa_bss *bss) { struct nai_realm *realm; u16 count, i; @@ -601,6 +822,14 @@ static int interworking_credentials_available(struct wpa_supplicant *wpa_s, } +static int interworking_credentials_available(struct wpa_supplicant *wpa_s, + struct wpa_bss *bss) +{ + return interworking_credentials_available_realm(wpa_s, bss) || + interworking_credentials_available_3gpp(wpa_s, bss); +} + + static void interworking_select_network(struct wpa_supplicant *wpa_s) { struct wpa_bss *bss, *selected = NULL;