Interworking: Add support for multiple credentials

This replaces the global home_* parameters with a list of credentials
that can be configured similarly to network blocks. For example:

cred={
	realm="example.com"
	username="user@example.com"
	password="password"
	ca_cert="/etc/wpa_supplicant/ca.pem"
	domain="example.com"
}

cred={
	imsi="310026-000000000"
	milenage="90dca4eda45b53cf0f12d7c9c3bc6a89:cb9cccc4b9258e6dca4760379fb82581:000000000123"
}

Signed-hostap: Jouni Malinen <jouni@qca.qualcomm.com>
This commit is contained in:
Jouni Malinen 2012-02-16 16:34:22 +02:00 committed by Jouni Malinen
parent 9914c96feb
commit 1bb7b8e84c
4 changed files with 321 additions and 126 deletions

View file

@ -1774,6 +1774,19 @@ void wpa_config_free_ssid(struct wpa_ssid *ssid)
}
void wpa_config_free_cred(struct wpa_cred *cred)
{
os_free(cred->realm);
os_free(cred->username);
os_free(cred->password);
os_free(cred->ca_cert);
os_free(cred->imsi);
os_free(cred->milenage);
os_free(cred->domain);
os_free(cred);
}
/**
* wpa_config_free - Free configuration data
* @config: Configuration data from wpa_config_read()
@ -1787,6 +1800,7 @@ void wpa_config_free(struct wpa_config *config)
struct wpa_config_blob *blob, *prevblob;
#endif /* CONFIG_NO_CONFIG_BLOBS */
struct wpa_ssid *ssid, *prev = NULL;
struct wpa_cred *cred, *cprev;
ssid = config->ssid;
while (ssid) {
@ -1795,6 +1809,13 @@ void wpa_config_free(struct wpa_config *config)
wpa_config_free_ssid(prev);
}
cred = config->cred;
while (cred) {
cprev = cred;
cred = cred->next;
wpa_config_free_cred(cprev);
}
#ifndef CONFIG_NO_CONFIG_BLOBS
blob = config->blobs;
prevblob = NULL;
@ -1819,13 +1840,6 @@ 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->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->home_domain);
os_free(config);
}
@ -2197,6 +2211,67 @@ void wpa_config_update_psk(struct wpa_ssid *ssid)
}
int wpa_config_set_cred(struct wpa_cred *cred, const char *var,
const char *value, int line)
{
char *val;
size_t len;
val = wpa_config_parse_string(value, &len);
if (val == NULL)
return -1;
if (os_strcmp(var, "realm") == 0) {
os_free(cred->realm);
cred->realm = val;
return 0;
}
if (os_strcmp(var, "username") == 0) {
os_free(cred->username);
cred->username = val;
return 0;
}
if (os_strcmp(var, "password") == 0) {
os_free(cred->password);
cred->password = val;
return 0;
}
if (os_strcmp(var, "ca_cert") == 0) {
os_free(cred->ca_cert);
cred->ca_cert = val;
return 0;
}
if (os_strcmp(var, "imsi") == 0) {
os_free(cred->imsi);
cred->imsi = val;
return 0;
}
if (os_strcmp(var, "milenage") == 0) {
os_free(cred->milenage);
cred->milenage = val;
return 0;
}
if (os_strcmp(var, "domain") == 0) {
os_free(cred->domain);
cred->domain = val;
return 0;
}
if (line) {
wpa_printf(MSG_ERROR, "Line %d: unknown cred field '%s'.",
line, var);
}
return -1;
}
#ifndef CONFIG_NO_CONFIG_BLOBS
/**
* wpa_config_get_blob - Get a named configuration blob
@ -2589,13 +2664,6 @@ 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 },
{ STR(home_username), 0 },
{ STR(home_password), 0 },
{ STR(home_ca_cert), 0 },
{ STR(home_imsi), 0 },
{ STR(home_milenage), 0 },
{ STR(home_domain), 0 },
{ INT_RANGE(interworking, 0, 1), 0 },
{ FUNC(hessid), 0 },
{ INT_RANGE(access_network_type, 0, 15), 0 }

View file

@ -28,6 +28,67 @@
#include "wps/wps.h"
struct wpa_cred {
/**
* next - Next credential in the list
*
* This pointer can be used to iterate over all credentials. The head
* of this list is stored in the cred field of struct wpa_config.
*/
struct wpa_cred *next;
/**
* id - Unique id for the credential
*
* This identifier is used as a unique identifier for each credential
* block when using the control interface. Each credential is allocated
* an id when it is being created, either when reading the
* configuration file or when a new credential is added through the
* control interface.
*/
int id;
/**
* realm - Home Realm for Interworking
*/
char *realm;
/**
* username - Username for Interworking network selection
*/
char *username;
/**
* password - Password for Interworking network selection
*/
char *password;
/**
* ca_cert - CA certificate for Interworking network selection
*/
char *ca_cert;
/**
* imsi - IMSI in <MCC> | <MNC> | '-' | <MSIN> format
*/
char *imsi;
/**
* milenage - Milenage parameters for SIM/USIM simulator in
* <Ki>:<OPc>:<SQN> format
*/
char *milenage;
/**
* domain - Home service provider FQDN
*
* This is used to compare against the Domain Name List to figure out
* whether the AP is operated by the Home SP.
*/
char *domain;
};
#define CFG_CHANGED_DEVICE_NAME BIT(0)
#define CFG_CHANGED_CONFIG_METHODS BIT(1)
#define CFG_CHANGED_DEVICE_TYPE BIT(2)
@ -71,6 +132,13 @@ struct wpa_config {
*/
int num_prio;
/**
* cred - Head of the credential list
*
* This is the head for the list of all the configured credentials.
*/
struct wpa_cred *cred;
/**
* eapol_version - IEEE 802.1X/EAPOL version number
*
@ -447,45 +515,6 @@ 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;
/**
* home_username - Username for Interworking network selection
*/
char *home_username;
/**
* home_password - Password for Interworking network selection
*/
char *home_password;
/**
* home_ca_cert - CA certificate for Interworking network selection
*/
char *home_ca_cert;
/**
* home_imsi - IMSI in <MCC> | <MNC> | '-' | <MSIN> format
*/
char *home_imsi;
/**
* home_milenage - Milenage parameters for SIM/USIM simulator in
* <Ki>:<OPc>:<SQN> format
*/
char *home_milenage;
/**
* home_domain - Home service provider FQDN
*
* This is used to compare against the Domain Name List to figure out
* whether the AP is operated by the Home SP.
*/
char *home_domain;
};
@ -518,6 +547,10 @@ void wpa_config_set_blob(struct wpa_config *config,
void wpa_config_free_blob(struct wpa_config_blob *blob);
int wpa_config_remove_blob(struct wpa_config *config, const char *name);
void wpa_config_free_cred(struct wpa_cred *cred);
int wpa_config_set_cred(struct wpa_cred *cred, const char *var,
const char *value, int line);
struct wpa_config * wpa_config_alloc_empty(const char *ctrl_interface,
const char *driver_param);
#ifndef CONFIG_NO_STDOUT_DEBUG

View file

@ -178,6 +178,61 @@ static struct wpa_ssid * wpa_config_read_network(FILE *f, int *line, int id)
}
static struct wpa_cred * wpa_config_read_cred(FILE *f, int *line, int id)
{
struct wpa_cred *cred;
int errors = 0, end = 0;
char buf[256], *pos, *pos2;
wpa_printf(MSG_MSGDUMP, "Line: %d - start of a new cred block", *line);
cred = os_zalloc(sizeof(*cred));
if (cred == NULL)
return NULL;
cred->id = id;
while (wpa_config_get_line(buf, sizeof(buf), f, line, &pos)) {
if (os_strcmp(pos, "}") == 0) {
end = 1;
break;
}
pos2 = os_strchr(pos, '=');
if (pos2 == NULL) {
wpa_printf(MSG_ERROR, "Line %d: Invalid cred line "
"'%s'.", *line, pos);
errors++;
continue;
}
*pos2++ = '\0';
if (*pos2 == '"') {
if (os_strchr(pos2 + 1, '"') == NULL) {
wpa_printf(MSG_ERROR, "Line %d: invalid "
"quotation '%s'.", *line, pos2);
errors++;
continue;
}
}
if (wpa_config_set_cred(cred, pos, pos2, *line) < 0)
errors++;
}
if (!end) {
wpa_printf(MSG_ERROR, "Line %d: cred block was not "
"terminated properly.", *line);
errors++;
}
if (errors) {
wpa_config_free_cred(cred);
cred = NULL;
}
return cred;
}
#ifndef CONFIG_NO_CONFIG_BLOBS
static struct wpa_config_blob * wpa_config_read_blob(FILE *f, int *line,
const char *name)
@ -267,8 +322,10 @@ struct wpa_config * wpa_config_read(const char *name)
char buf[256], *pos;
int errors = 0, line = 0;
struct wpa_ssid *ssid, *tail = NULL, *head = NULL;
struct wpa_cred *cred, *cred_tail = NULL, *cred_head = NULL;
struct wpa_config *config;
int id = 0;
int cred_id = 0;
config = wpa_config_alloc_empty(NULL, NULL);
if (config == NULL)
@ -302,6 +359,20 @@ struct wpa_config * wpa_config_read(const char *name)
errors++;
continue;
}
} else if (os_strcmp(pos, "cred={") == 0) {
cred = wpa_config_read_cred(f, &line, cred_id++);
if (cred == NULL) {
wpa_printf(MSG_ERROR, "Line %d: failed to "
"parse cred block.", line);
errors++;
continue;
}
if (cred_head == NULL) {
cred_head = cred_tail = cred;
} else {
cred_tail->next = cred;
cred_tail = cred;
}
#ifndef CONFIG_NO_CONFIG_BLOBS
} else if (os_strncmp(pos, "blob-base64-", 12) == 0) {
if (wpa_config_process_blob(config, f, &line, pos + 12)
@ -322,6 +393,7 @@ struct wpa_config * wpa_config_read(const char *name)
config->ssid = head;
wpa_config_debug_dump_networks(config);
config->cred = cred_head;
#ifndef WPA_IGNORE_CONFIG_ERRORS
if (errors) {
@ -712,18 +784,6 @@ static void wpa_config_write_global(FILE *f, struct wpa_config *config)
if (config->disassoc_low_ack)
fprintf(f, "disassoc_low_ack=%u\n", config->disassoc_low_ack);
#ifdef CONFIG_INTERWORKING
if (config->home_realm)
fprintf(f, "home_realm=%s\n", config->home_realm);
if (config->home_username)
fprintf(f, "home_username=%s\n", config->home_username);
if (config->home_password)
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))

View file

@ -416,15 +416,16 @@ static int nai_realm_cred_username(struct nai_realm_eap *eap)
}
static struct nai_realm_eap * nai_realm_find_eap(struct wpa_supplicant *wpa_s,
static struct nai_realm_eap * nai_realm_find_eap(struct wpa_cred *cred,
struct nai_realm *realm)
{
u8 e;
if (wpa_s->conf->home_username == NULL ||
wpa_s->conf->home_username[0] == '\0' ||
wpa_s->conf->home_password == NULL ||
wpa_s->conf->home_password[0] == '\0')
if (cred == NULL ||
cred->username == NULL ||
cred->username[0] == '\0' ||
cred->password == NULL ||
cred->password[0] == '\0')
return NULL;
for (e = 0; e < realm->eap_count; e++) {
@ -571,9 +572,23 @@ static int interworking_connect_3gpp(struct wpa_supplicant *wpa_s,
struct wpa_bss *bss)
{
#ifdef INTERWORKING_3GPP
struct wpa_cred *cred;
struct wpa_ssid *ssid;
const u8 *ie;
if (bss->anqp_3gpp == NULL)
return -1;
for (cred = wpa_s->conf->cred; cred; cred = cred->next) {
if (cred->imsi == NULL || !cred->imsi[0] ||
cred->milenage == NULL || !cred->milenage[0])
continue;
if (plmn_id_match(bss->anqp_3gpp, cred->imsi))
break;
}
if (cred == NULL)
return -1;
ie = wpa_bss_get_ie(bss, WLAN_EID_SSID);
if (ie == NULL)
return -1;
@ -598,14 +613,14 @@ static int interworking_connect_3gpp(struct wpa_supplicant *wpa_s,
wpa_printf(MSG_DEBUG, "EAP-SIM not supported");
goto fail;
}
if (set_root_nai(ssid, wpa_s->conf->home_imsi, '1') < 0) {
if (set_root_nai(ssid, cred->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 (cred->milenage && cred->milenage[0]) {
if (wpa_config_set_quoted(ssid, "password",
wpa_s->conf->home_milenage) < 0)
cred->milenage) < 0)
goto fail;
} else {
/* TODO: PIN */
@ -613,9 +628,8 @@ static int interworking_connect_3gpp(struct wpa_supplicant *wpa_s,
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)
if (cred->password && cred->password[0] &&
wpa_config_set_quoted(ssid, "password", cred->password) < 0)
goto fail;
wpa_s->disconnected = 0;
@ -634,6 +648,7 @@ fail:
int interworking_connect(struct wpa_supplicant *wpa_s, struct wpa_bss *bss)
{
struct wpa_cred *cred;
struct wpa_ssid *ssid;
struct nai_realm *realm;
struct nai_realm_eap *eap = NULL;
@ -641,7 +656,7 @@ int interworking_connect(struct wpa_supplicant *wpa_s, struct wpa_bss *bss)
char buf[100];
const u8 *ie;
if (bss == NULL)
if (wpa_s->conf->cred == NULL || bss == NULL)
return -1;
ie = wpa_bss_get_ie(bss, WLAN_EID_SSID);
if (ie == NULL || ie[1] == 0) {
@ -657,10 +672,14 @@ int interworking_connect(struct wpa_supplicant *wpa_s, struct wpa_bss *bss)
count = 0;
}
for (i = 0; i < count; i++) {
if (!nai_realm_match(&realm[i], wpa_s->conf->home_realm))
continue;
eap = nai_realm_find_eap(wpa_s, &realm[i]);
for (cred = wpa_s->conf->cred; cred; cred = cred->next) {
for (i = 0; i < count; i++) {
if (!nai_realm_match(&realm[i], cred->realm))
continue;
eap = nai_realm_find_eap(cred, &realm[i]);
if (eap)
break;
}
if (eap)
break;
}
@ -701,11 +720,11 @@ int interworking_connect(struct wpa_supplicant *wpa_s, struct wpa_bss *bss)
goto fail;
if (eap->method == EAP_TYPE_TTLS &&
wpa_s->conf->home_username && wpa_s->conf->home_username[0]) {
cred->username && cred->username[0]) {
const char *pos;
char *anon;
/* Use anonymous NAI in Phase 1 */
pos = os_strchr(wpa_s->conf->home_username, '@');
pos = os_strchr(cred->username, '@');
if (pos) {
size_t buflen = 9 + os_strlen(pos) + 1;
anon = os_malloc(buflen);
@ -725,14 +744,12 @@ int interworking_connect(struct wpa_supplicant *wpa_s, struct wpa_bss *bss)
os_free(anon);
}
if (wpa_s->conf->home_username && wpa_s->conf->home_username[0] &&
wpa_config_set_quoted(ssid, "identity",
wpa_s->conf->home_username) < 0)
if (cred->username && cred->username[0] &&
wpa_config_set_quoted(ssid, "identity", cred->username) < 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)
if (cred->password && cred->password[0] &&
wpa_config_set_quoted(ssid, "password", cred->password) < 0)
goto fail;
switch (eap->method) {
@ -776,9 +793,8 @@ int interworking_connect(struct wpa_supplicant *wpa_s, struct wpa_bss *bss)
break;
}
if (wpa_s->conf->home_ca_cert && wpa_s->conf->home_ca_cert[0] &&
wpa_config_set_quoted(ssid, "ca_cert", wpa_s->conf->home_ca_cert) <
0)
if (cred->ca_cert && cred->ca_cert[0] &&
wpa_config_set_quoted(ssid, "ca_cert", cred->ca_cert) < 0)
goto fail;
nai_realm_free(realm, count);
@ -800,29 +816,34 @@ fail:
static int interworking_credentials_available_3gpp(
struct wpa_supplicant *wpa_s, struct wpa_bss *bss)
{
int ret = 0;
struct wpa_cred *cred;
int ret;
#ifdef INTERWORKING_3GPP
if (bss->anqp_3gpp == NULL)
return ret;
return 0;
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;
for (cred = wpa_s->conf->cred; cred; cred = cred->next) {
if (cred->imsi == NULL || !cred->imsi[0] ||
cred->milenage == NULL || !cred->milenage[0])
continue;
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 ");
wpa_printf(MSG_DEBUG, "Interworking: Parsing 3GPP info from "
MACSTR, MAC2STR(bss->bssid));
ret = plmn_id_match(bss->anqp_3gpp, cred->imsi);
wpa_printf(MSG_DEBUG, "PLMN match %sfound", ret ? "" : "not ");
if (ret)
return 1;
}
#endif /* INTERWORKING_3GPP */
return ret;
return 0;
}
static int interworking_credentials_available_realm(
struct wpa_supplicant *wpa_s, struct wpa_bss *bss)
{
struct wpa_cred *cred;
struct nai_realm *realm;
u16 count, i;
int found = 0;
@ -830,7 +851,7 @@ static int interworking_credentials_available_realm(
if (bss->anqp_nai_realm == NULL)
return 0;
if (wpa_s->conf->home_realm == NULL)
if (wpa_s->conf->cred == NULL)
return 0;
wpa_printf(MSG_DEBUG, "Interworking: Parsing NAI Realm list from "
@ -842,13 +863,20 @@ static int interworking_credentials_available_realm(
return 0;
}
for (i = 0; i < count; i++) {
if (!nai_realm_match(&realm[i], wpa_s->conf->home_realm))
for (cred = wpa_s->conf->cred; cred; cred = cred->next) {
if (cred->realm == NULL)
continue;
if (nai_realm_find_eap(wpa_s, &realm[i])) {
found++;
break;
for (i = 0; i < count; i++) {
if (!nai_realm_match(&realm[i], cred->realm))
continue;
if (nai_realm_find_eap(cred, &realm[i])) {
found++;
break;
}
}
if (found)
break;
}
nai_realm_free(realm, count);
@ -895,33 +923,39 @@ static int domain_name_list_contains(struct wpabuf *domain_names,
static int interworking_home_sp(struct wpa_supplicant *wpa_s,
struct wpabuf *domain_names)
{
struct wpa_cred *cred;
#ifdef INTERWORKING_3GPP
char nai[100], *realm;
#endif /* INTERWORKING_3GPP */
if (domain_names == NULL)
if (domain_names == NULL || wpa_s->conf->cred == NULL)
return -1;
for (cred = wpa_s->conf->cred; cred; cred = cred->next) {
#ifdef INTERWORKING_3GPP
if (wpa_s->conf->home_imsi &&
build_root_nai(nai, wpa_s->conf->home_imsi, 0) == 0) {
realm = os_strchr(nai, '@');
if (realm)
realm++;
wpa_printf(MSG_DEBUG, "Interworking: Search for match with "
"SIM/USIM domain %s", realm);
if (realm && domain_name_list_contains(domain_names, realm))
return 1;
}
if (cred->imsi &&
build_root_nai(nai, cred->imsi, 0) == 0) {
realm = os_strchr(nai, '@');
if (realm)
realm++;
wpa_printf(MSG_DEBUG, "Interworking: Search for match "
"with SIM/USIM domain %s", realm);
if (realm &&
domain_name_list_contains(domain_names, realm))
return 1;
}
#endif /* INTERWORKING_3GPP */
if (wpa_s->conf->home_domain == NULL)
return -1;
if (cred->domain == NULL)
continue;
wpa_printf(MSG_DEBUG, "Interworking: Search for match with "
"home SP FQDN %s", wpa_s->conf->home_domain);
return domain_name_list_contains(domain_names,
wpa_s->conf->home_domain);
wpa_printf(MSG_DEBUG, "Interworking: Search for match with "
"home SP FQDN %s", cred->domain);
if (domain_name_list_contains(domain_names, cred->domain))
return 1;
}
return 0;
}