diff options
author | sam <sam@FreeBSD.org> | 2007-07-09 16:15:06 +0000 |
---|---|---|
committer | sam <sam@FreeBSD.org> | 2007-07-09 16:15:06 +0000 |
commit | 1bf2fd00c50865c26197a0fb9ce70f417b9fa121 (patch) | |
tree | c6f336fc28b042f00efc2373c71fceadfa394e52 /contrib/hostapd/eap_sim_db.c | |
parent | 620bfba12034be7d2ad4a357063d609ff5b6e63a (diff) | |
download | FreeBSD-src-1bf2fd00c50865c26197a0fb9ce70f417b9fa121.zip FreeBSD-src-1bf2fd00c50865c26197a0fb9ce70f417b9fa121.tar.gz |
Import of hostapd 0.5.8
Diffstat (limited to 'contrib/hostapd/eap_sim_db.c')
-rw-r--r-- | contrib/hostapd/eap_sim_db.c | 1284 |
1 files changed, 1147 insertions, 137 deletions
diff --git a/contrib/hostapd/eap_sim_db.c b/contrib/hostapd/eap_sim_db.c index a965fa4..93ade14 100644 --- a/contrib/hostapd/eap_sim_db.c +++ b/contrib/hostapd/eap_sim_db.c @@ -1,6 +1,6 @@ /* * hostapd / EAP-SIM database/authenticator gateway - * Copyright (c) 2005, Jouni Malinen <jkmaline@cc.hut.fi> + * Copyright (c) 2005-2006, Jouni Malinen <j@w1.fi> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as @@ -10,106 +10,555 @@ * license. * * See README and COPYING for more details. - */ - -/* This is an example implementation of the EAP-SIM database/authentication - * gateway interface that is expected to be replaced with an implementation of - * SS7 gateway to GSM authentication center (HLR/AuC) or a local - * implementation of SIM triplet generator. * - * The example implementation here reads triplets from a text file in - * IMSI:Kc:SRES:RAND format, IMSI in ASCII, other fields as hex strings. This - * is used to simulate an HLR/AuC. As such, it is not very useful for real life - * authentication, but it is useful both as an example implementation and for - * EAP-SIM testing. + * This is an example implementation of the EAP-SIM/AKA database/authentication + * gateway interface that is using an external program as an SS7 gateway to + * GSM/UMTS authentication center (HLR/AuC). hlr_auc_gw is an example + * implementation of such a gateway program. This eap_sim_db.c takes care of + * EAP-SIM/AKA pseudonyms and re-auth identities. It can be used with different + * gateway implementations for HLR/AuC access. Alternatively, it can also be + * completely replaced if the in-memory database of pseudonyms/re-auth + * identities is not suitable for some cases. */ -#include <stdlib.h> -#include <stdio.h> -#include <string.h> +#include "includes.h" +#include <sys/un.h> #include "common.h" #include "eap_sim_common.h" #include "eap_sim_db.h" +#include "eloop.h" +struct eap_sim_pseudonym { + struct eap_sim_pseudonym *next; + u8 *identity; + size_t identity_len; + char *pseudonym; +}; -/* TODO: add an alternative callback based version of the interface. This is - * needed to work better with the single threaded design of hostapd. For this, - * the EAP data has to be stored somewhere and eap_sim_db is given a context - * pointer for this and a callback function. The callback function will re-send - * the EAP data through normal operations which will eventually end up calling - * eap_sim_db_get_gsm_triplets() again for the same user. This time, eap_sim_db - * should have the triplets available immediately. */ - +struct eap_sim_db_pending { + struct eap_sim_db_pending *next; + u8 imsi[20]; + size_t imsi_len; + enum { PENDING, SUCCESS, FAILURE } state; + void *cb_session_ctx; + struct os_time timestamp; + int aka; + union { + struct { + u8 kc[EAP_SIM_MAX_CHAL][EAP_SIM_KC_LEN]; + u8 sres[EAP_SIM_MAX_CHAL][EAP_SIM_SRES_LEN]; + u8 rand[EAP_SIM_MAX_CHAL][GSM_RAND_LEN]; + int num_chal; + } sim; + struct { + u8 rand[EAP_AKA_RAND_LEN]; + u8 autn[EAP_AKA_AUTN_LEN]; + u8 ik[EAP_AKA_IK_LEN]; + u8 ck[EAP_AKA_CK_LEN]; + u8 res[EAP_AKA_RES_MAX_LEN]; + size_t res_len; + } aka; + } u; +}; struct eap_sim_db_data { + int sock; char *fname; + char *local_sock; + void (*get_complete_cb)(void *ctx, void *session_ctx); + void *ctx; + struct eap_sim_pseudonym *pseudonyms; + struct eap_sim_reauth *reauths; + struct eap_sim_db_pending *pending; }; -#define KC_LEN 8 -#define SRES_LEN 4 -#define RAND_LEN 16 +static struct eap_sim_db_pending * +eap_sim_db_get_pending(struct eap_sim_db_data *data, const u8 *imsi, + size_t imsi_len, int aka) +{ + struct eap_sim_db_pending *entry, *prev = NULL; + + entry = data->pending; + while (entry) { + if (entry->aka == aka && entry->imsi_len == imsi_len && + memcmp(entry->imsi, imsi, imsi_len) == 0) { + if (prev) + prev->next = entry->next; + else + data->pending = entry->next; + break; + } + prev = entry; + entry = entry->next; + } + return entry; +} -/* Initialize EAP-SIM database/authentication gateway interface. - * Returns pointer to a private data structure. */ -void * eap_sim_db_init(const char *config) + +static void eap_sim_db_add_pending(struct eap_sim_db_data *data, + struct eap_sim_db_pending *entry) +{ + entry->next = data->pending; + data->pending = entry; +} + + +static void eap_sim_db_sim_resp_auth(struct eap_sim_db_data *data, + const char *imsi, char *buf) +{ + char *start, *end, *pos; + struct eap_sim_db_pending *entry; + int num_chal; + + /* + * SIM-RESP-AUTH <IMSI> Kc(i):SRES(i):RAND(i) ... + * SIM-RESP-AUTH <IMSI> FAILURE + * (IMSI = ASCII string, Kc/SRES/RAND = hex string) + */ + + entry = eap_sim_db_get_pending(data, (u8 *) imsi, strlen(imsi), 0); + if (entry == NULL) { + wpa_printf(MSG_DEBUG, "EAP-SIM DB: No pending entry for the " + "received message found"); + return; + } + + start = buf; + if (strncmp(start, "FAILURE", 7) == 0) { + wpa_printf(MSG_DEBUG, "EAP-SIM DB: External server reported " + "failure"); + entry->state = FAILURE; + eap_sim_db_add_pending(data, entry); + data->get_complete_cb(data->ctx, entry->cb_session_ctx); + return; + } + + num_chal = 0; + while (num_chal < EAP_SIM_MAX_CHAL) { + end = strchr(start, ' '); + if (end) + *end = '\0'; + + pos = strchr(start, ':'); + if (pos == NULL) + goto parse_fail; + *pos = '\0'; + if (hexstr2bin(start, entry->u.sim.kc[num_chal], + EAP_SIM_KC_LEN)) + goto parse_fail; + + start = pos + 1; + pos = strchr(start, ':'); + if (pos == NULL) + goto parse_fail; + *pos = '\0'; + if (hexstr2bin(start, entry->u.sim.sres[num_chal], + EAP_SIM_SRES_LEN)) + goto parse_fail; + + start = pos + 1; + if (hexstr2bin(start, entry->u.sim.rand[num_chal], + GSM_RAND_LEN)) + goto parse_fail; + + num_chal++; + if (end == NULL) + break; + else + start = end + 1; + } + entry->u.sim.num_chal = num_chal; + + entry->state = SUCCESS; + wpa_printf(MSG_DEBUG, "EAP-SIM DB: Authentication data parsed " + "successfully - callback"); + eap_sim_db_add_pending(data, entry); + data->get_complete_cb(data->ctx, entry->cb_session_ctx); + return; + +parse_fail: + wpa_printf(MSG_DEBUG, "EAP-SIM DB: Failed to parse response string"); + free(entry); +} + + +static void eap_sim_db_aka_resp_auth(struct eap_sim_db_data *data, + const char *imsi, char *buf) +{ + char *start, *end; + struct eap_sim_db_pending *entry; + + /* + * AKA-RESP-AUTH <IMSI> <RAND> <AUTN> <IK> <CK> <RES> + * AKA-RESP-AUTH <IMSI> FAILURE + * (IMSI = ASCII string, RAND/AUTN/IK/CK/RES = hex string) + */ + + entry = eap_sim_db_get_pending(data, (u8 *) imsi, strlen(imsi), 1); + if (entry == NULL) { + wpa_printf(MSG_DEBUG, "EAP-SIM DB: No pending entry for the " + "received message found"); + return; + } + + start = buf; + if (strncmp(start, "FAILURE", 7) == 0) { + wpa_printf(MSG_DEBUG, "EAP-SIM DB: External server reported " + "failure"); + entry->state = FAILURE; + eap_sim_db_add_pending(data, entry); + data->get_complete_cb(data->ctx, entry->cb_session_ctx); + return; + } + + end = strchr(start, ' '); + if (end == NULL) + goto parse_fail; + *end = '\0'; + if (hexstr2bin(start, entry->u.aka.rand, EAP_AKA_RAND_LEN)) + goto parse_fail; + + start = end + 1; + end = strchr(start, ' '); + if (end == NULL) + goto parse_fail; + *end = '\0'; + if (hexstr2bin(start, entry->u.aka.autn, EAP_AKA_AUTN_LEN)) + goto parse_fail; + + start = end + 1; + end = strchr(start, ' '); + if (end == NULL) + goto parse_fail; + *end = '\0'; + if (hexstr2bin(start, entry->u.aka.ik, EAP_AKA_IK_LEN)) + goto parse_fail; + + start = end + 1; + end = strchr(start, ' '); + if (end == NULL) + goto parse_fail; + *end = '\0'; + if (hexstr2bin(start, entry->u.aka.ck, EAP_AKA_CK_LEN)) + goto parse_fail; + + start = end + 1; + end = strchr(start, ' '); + if (end) + *end = '\0'; + else { + end = start; + while (*end) + end++; + } + entry->u.aka.res_len = (end - start) / 2; + if (entry->u.aka.res_len > EAP_AKA_RES_MAX_LEN) { + wpa_printf(MSG_DEBUG, "EAP-SIM DB: Too long RES"); + entry->u.aka.res_len = 0; + goto parse_fail; + } + if (hexstr2bin(start, entry->u.aka.res, entry->u.aka.res_len)) + goto parse_fail; + + entry->state = SUCCESS; + wpa_printf(MSG_DEBUG, "EAP-SIM DB: Authentication data parsed " + "successfully - callback"); + eap_sim_db_add_pending(data, entry); + data->get_complete_cb(data->ctx, entry->cb_session_ctx); + return; + +parse_fail: + wpa_printf(MSG_DEBUG, "EAP-SIM DB: Failed to parse response string"); + free(entry); +} + + +static void eap_sim_db_receive(int sock, void *eloop_ctx, void *sock_ctx) +{ + struct eap_sim_db_data *data = eloop_ctx; + char buf[1000], *pos, *cmd, *imsi; + int res; + + res = recv(sock, buf, sizeof(buf), 0); + if (res < 0) + return; + wpa_hexdump_ascii_key(MSG_MSGDUMP, "EAP-SIM DB: Received from an " + "external source", (u8 *) buf, res); + if (res == 0) + return; + if (res >= (int) sizeof(buf)) + res = sizeof(buf) - 1; + buf[res] = '\0'; + + if (data->get_complete_cb == NULL) { + wpa_printf(MSG_DEBUG, "EAP-SIM DB: No get_complete_cb " + "registered"); + return; + } + + /* <cmd> <IMSI> ... */ + + cmd = buf; + pos = strchr(cmd, ' '); + if (pos == NULL) + goto parse_fail; + *pos = '\0'; + imsi = pos + 1; + pos = strchr(imsi, ' '); + if (pos == NULL) + goto parse_fail; + *pos = '\0'; + wpa_printf(MSG_DEBUG, "EAP-SIM DB: External response=%s for IMSI %s", + cmd, imsi); + + if (strcmp(cmd, "SIM-RESP-AUTH") == 0) + eap_sim_db_sim_resp_auth(data, imsi, pos + 1); + else if (strcmp(cmd, "AKA-RESP-AUTH") == 0) + eap_sim_db_aka_resp_auth(data, imsi, pos + 1); + else + wpa_printf(MSG_INFO, "EAP-SIM DB: Unknown external response " + "'%s'", cmd); + return; + +parse_fail: + wpa_printf(MSG_DEBUG, "EAP-SIM DB: Failed to parse response string"); +} + + +static int eap_sim_db_open_socket(struct eap_sim_db_data *data) +{ + struct sockaddr_un addr; + static int counter = 0; + + if (strncmp(data->fname, "unix:", 5) != 0) + return -1; + + data->sock = socket(PF_UNIX, SOCK_DGRAM, 0); + if (data->sock < 0) { + perror("socket(eap_sim_db)"); + return -1; + } + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + snprintf(addr.sun_path, sizeof(addr.sun_path), + "/tmp/eap_sim_db_%d-%d", getpid(), counter++); + data->local_sock = strdup(addr.sun_path); + if (bind(data->sock, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + perror("bind(eap_sim_db)"); + close(data->sock); + data->sock = -1; + return -1; + } + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + snprintf(addr.sun_path, sizeof(addr.sun_path), "%s", data->fname + 5); + if (connect(data->sock, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + perror("connect(eap_sim_db)"); + wpa_hexdump_ascii(MSG_INFO, "HLR/AuC GW socket", + (u8 *) addr.sun_path, strlen(addr.sun_path)); + close(data->sock); + data->sock = -1; + return -1; + } + + eloop_register_read_sock(data->sock, eap_sim_db_receive, data, NULL); + + return 0; +} + + +static void eap_sim_db_close_socket(struct eap_sim_db_data *data) +{ + if (data->sock >= 0) { + eloop_unregister_read_sock(data->sock); + close(data->sock); + data->sock = -1; + } + if (data->local_sock) { + unlink(data->local_sock); + free(data->local_sock); + data->local_sock = NULL; + } +} + + +/** + * eap_sim_db_init - Initialize EAP-SIM DB / authentication gateway interface + * @config: Configuration data (e.g., file name) + * @get_complete_cb: Callback function for reporting availability of triplets + * @ctx: Context pointer for get_complete_cb + * Returns: Pointer to a private data structure or %NULL on failure + */ +void * eap_sim_db_init(const char *config, + void (*get_complete_cb)(void *ctx, void *session_ctx), + void *ctx) { struct eap_sim_db_data *data; - data = malloc(sizeof(*data)); - if (data == NULL) { + data = wpa_zalloc(sizeof(*data)); + if (data == NULL) return NULL; - } - memset(data, 0, sizeof(*data)); + data->sock = -1; + data->get_complete_cb = get_complete_cb; + data->ctx = ctx; data->fname = strdup(config); - if (data->fname == NULL) { - free(data); - return NULL; + if (data->fname == NULL) + goto fail; + + if (strncmp(data->fname, "unix:", 5) == 0) { + if (eap_sim_db_open_socket(data)) + goto fail; } return data; + +fail: + eap_sim_db_close_socket(data); + free(data->fname); + free(data); + return NULL; +} + + +static void eap_sim_db_free_pseudonym(struct eap_sim_pseudonym *p) +{ + free(p->identity); + free(p->pseudonym); + free(p); +} + + +static void eap_sim_db_free_reauth(struct eap_sim_reauth *r) +{ + free(r->identity); + free(r->reauth_id); + free(r); } -/* Deinitialize EAP-SIM database/authentication gateway interface. - * priv is the pointer from eap_sim_db_init(). */ + +/** + * eap_sim_db_deinit - Deinitialize EAP-SIM DB/authentication gw interface + * @priv: Private data pointer from eap_sim_db_init() + */ void eap_sim_db_deinit(void *priv) { struct eap_sim_db_data *data = priv; + struct eap_sim_pseudonym *p, *prev; + struct eap_sim_reauth *r, *prevr; + struct eap_sim_db_pending *pending, *prev_pending; + + eap_sim_db_close_socket(data); free(data->fname); + + p = data->pseudonyms; + while (p) { + prev = p; + p = p->next; + eap_sim_db_free_pseudonym(prev); + } + + r = data->reauths; + while (r) { + prevr = r; + r = r->next; + eap_sim_db_free_reauth(prevr); + } + + pending = data->pending; + while (pending) { + prev_pending = pending; + pending = pending->next; + free(prev_pending); + } + free(data); } -/* Get GSM triplets for user name identity (identity_len bytes). In most cases, - * the user name is '1' | IMSI, i.e., 1 followed by the IMSI in ASCII format. - * The identity may also include NAI realm (@realm). - * priv is the pointer from eap_sim_db_init(). - * Returns the number of triplets received (has to be less than or equal to - * max_chal) or -1 on error (e.g., user not found). rand, kc, and sres are - * pointers to data areas for the triplets. */ +static int eap_sim_db_send(struct eap_sim_db_data *data, const char *msg, + size_t len) +{ + int _errno = 0; + + if (send(data->sock, msg, len, 0) < 0) { + _errno = errno; + perror("send[EAP-SIM DB UNIX]"); + } + + if (_errno == ENOTCONN || _errno == EDESTADDRREQ || _errno == EINVAL || + _errno == ECONNREFUSED) { + /* Try to reconnect */ + eap_sim_db_close_socket(data); + if (eap_sim_db_open_socket(data) < 0) + return -1; + wpa_printf(MSG_DEBUG, "EAP-SIM DB: Reconnected to the " + "external server"); + if (send(data->sock, msg, len, 0) < 0) { + perror("send[EAP-SIM DB UNIX]"); + return -1; + } + } + + return 0; +} + + +static void eap_sim_db_expire_pending(struct eap_sim_db_data *data) +{ + /* TODO: add limit for maximum length for pending list; remove latest + * (i.e., last) entry from the list if the limit is reached; could also + * use timeout to expire pending entries */ +} + + +/** + * eap_sim_db_get_gsm_triplets - Get GSM triplets + * @priv: Private data pointer from eap_sim_db_init() + * @identity: User name identity + * @identity_len: Length of identity in bytes + * @max_chal: Maximum number of triplets + * @_rand: Buffer for RAND values + * @kc: Buffer for Kc values + * @sres: Buffer for SRES values + * @cb_session_ctx: Session callback context for get_complete_cb() + * Returns: Number of triplets received (has to be less than or equal to + * max_chal), -1 (EAP_SIM_DB_FAILURE) on error (e.g., user not found), or + * -2 (EAP_SIM_DB_PENDING) if results are not yet available. In this case, the + * callback function registered with eap_sim_db_init() will be called once the + * results become available. + * + * In most cases, the user name is '1' | IMSI, i.e., 1 followed by the IMSI in + * ASCII format. + * + * When using an external server for GSM triplets, this function can always + * start a request and return EAP_SIM_DB_PENDING immediately if authentication + * triplets are not available. Once the triplets are received, callback + * function registered with eap_sim_db_init() is called to notify EAP state + * machine to reprocess the message. This eap_sim_db_get_gsm_triplets() + * function will then be called again and the newly received triplets will then + * be given to the caller. + */ int eap_sim_db_get_gsm_triplets(void *priv, const u8 *identity, size_t identity_len, int max_chal, - u8 *rand, u8 *kc, u8 *sres) + u8 *_rand, u8 *kc, u8 *sres, + void *cb_session_ctx) { struct eap_sim_db_data *data = priv; - FILE *f; - int count, i; - char buf[80], *pos, *next; - - f = fopen(data->fname, "r"); - if (f == NULL) { - wpa_printf(MSG_DEBUG, "EAP-SIM DB: could not open triplet " - "file '%s'", data->fname); - return -1; - } + struct eap_sim_db_pending *entry; + int len, ret; + size_t i; + char msg[40]; - if (identity_len < 2 || identity[0] != '1') { + if (identity_len < 2 || identity[0] != EAP_SIM_PERMANENT_PREFIX || + identity_len + 1 > sizeof(entry->imsi)) { wpa_hexdump_ascii(MSG_DEBUG, "EAP-SIM DB: unexpected identity", identity, identity_len); - fclose(f); - return -1; + return EAP_SIM_DB_FAILURE; } identity++; identity_len--; @@ -119,90 +568,560 @@ int eap_sim_db_get_gsm_triplets(void *priv, const u8 *identity, break; } } - wpa_hexdump_ascii(MSG_DEBUG, "EAP-SIM DB: get triplets for IMSI", + wpa_hexdump_ascii(MSG_DEBUG, "EAP-SIM DB: Get GSM triplets for IMSI", identity, identity_len); - count = 0; - while (count < max_chal && fgets(buf, sizeof(buf), f)) { - /* Parse IMSI:Kc:SRES:RAND and match IMSI with identity. */ - buf[sizeof(buf) - 1] = '\0'; - pos = buf; - while (*pos != '\0' && *pos != '\n') - pos++; - if (*pos == '\n') - *pos = '\0'; - if (pos - buf < 60 || pos[0] == '#') - continue; - - pos = strchr(buf, ':'); - if (pos == NULL) - continue; - *pos++ = '\0'; - if (strlen(buf) != identity_len || - memcmp(buf, identity, identity_len) != 0) - continue; + entry = eap_sim_db_get_pending(data, identity, identity_len, 0); + if (entry) { + int num_chal; + if (entry->state == FAILURE) { + wpa_printf(MSG_DEBUG, "EAP-SIM DB: Pending entry -> " + "failure"); + free(entry); + return EAP_SIM_DB_FAILURE; + } + + if (entry->state == PENDING) { + wpa_printf(MSG_DEBUG, "EAP-SIM DB: Pending entry -> " + "still pending"); + eap_sim_db_add_pending(data, entry); + return EAP_SIM_DB_PENDING; + } + + wpa_printf(MSG_DEBUG, "EAP-SIM DB: Pending entry -> " + "%d challenges", entry->u.sim.num_chal); + num_chal = entry->u.sim.num_chal; + if (num_chal > max_chal) + num_chal = max_chal; + memcpy(_rand, entry->u.sim.rand, num_chal * GSM_RAND_LEN); + memcpy(sres, entry->u.sim.sres, num_chal * EAP_SIM_SRES_LEN); + memcpy(kc, entry->u.sim.kc, num_chal * EAP_SIM_KC_LEN); + free(entry); + return num_chal; + } + + if (data->sock < 0) { + if (eap_sim_db_open_socket(data) < 0) + return EAP_SIM_DB_FAILURE; + } + + len = snprintf(msg, sizeof(msg), "SIM-REQ-AUTH "); + if (len < 0 || len + identity_len >= sizeof(msg)) + return EAP_SIM_DB_FAILURE; + memcpy(msg + len, identity, identity_len); + len += identity_len; + ret = snprintf(msg + len, sizeof(msg) - len, " %d", max_chal); + if (ret < 0 || (size_t) ret >= sizeof(msg) - len) + return EAP_SIM_DB_FAILURE; + len += ret; + + wpa_hexdump(MSG_DEBUG, "EAP-SIM DB: requesting SIM authentication " + "data for IMSI", identity, identity_len); + if (eap_sim_db_send(data, msg, len) < 0) + return EAP_SIM_DB_FAILURE; + + entry = wpa_zalloc(sizeof(*entry)); + if (entry == NULL) + return EAP_SIM_DB_FAILURE; + + os_get_time(&entry->timestamp); + memcpy(entry->imsi, identity, identity_len); + entry->imsi_len = identity_len; + entry->cb_session_ctx = cb_session_ctx; + entry->state = PENDING; + eap_sim_db_add_pending(data, entry); + eap_sim_db_expire_pending(data); - next = strchr(pos, ':'); - if (next == NULL) - continue; - *next++ = '\0'; - if (hexstr2bin(pos, &kc[count * KC_LEN], KC_LEN) < 0) - continue; + return EAP_SIM_DB_PENDING; +} - pos = next; - next = strchr(pos, ':'); - if (next == NULL) - continue; - *next++ = '\0'; - if (hexstr2bin(pos, &sres[count * SRES_LEN], SRES_LEN) < 0) - continue; - if (hexstr2bin(next, &rand[count * RAND_LEN], RAND_LEN) < 0) - continue; +static struct eap_sim_pseudonym * +eap_sim_db_get_pseudonym(struct eap_sim_db_data *data, const u8 *identity, + size_t identity_len) +{ + char *pseudonym; + size_t len; + struct eap_sim_pseudonym *p; - count++; + if (identity_len == 0 || + (identity[0] != EAP_SIM_PSEUDONYM_PREFIX && + identity[0] != EAP_AKA_PSEUDONYM_PREFIX)) + return NULL; + + /* Remove possible realm from identity */ + len = 0; + while (len < identity_len) { + if (identity[len] == '@') + break; + len++; } - fclose(f); + pseudonym = malloc(len + 1); + if (pseudonym == NULL) + return NULL; + memcpy(pseudonym, identity, len); + pseudonym[len] = '\0'; - if (count == 0) { - wpa_printf(MSG_DEBUG, "EAP-SIM DB: no triplets found"); - count = -1; + p = data->pseudonyms; + while (p) { + if (strcmp(p->pseudonym, pseudonym) == 0) + break; + p = p->next; } - return count; + free(pseudonym); + + return p; } -/* Verify whether the given user identity (identity_len bytes) is known. In - * most cases, the user name is '1' | IMSI, i.e., 1 followed by the IMSI in - * ASCII format. - * priv is the pointer from eap_sim_db_init(). - * Returns 0 if the user is found and GSM triplets would be available for it or - * -1 on error (e.g., user not found or no triplets available). */ +static struct eap_sim_pseudonym * +eap_sim_db_get_pseudonym_id(struct eap_sim_db_data *data, const u8 *identity, + size_t identity_len) +{ + struct eap_sim_pseudonym *p; + + if (identity_len == 0 || + (identity[0] != EAP_SIM_PERMANENT_PREFIX && + identity[0] != EAP_AKA_PERMANENT_PREFIX)) + return NULL; + + p = data->pseudonyms; + while (p) { + if (identity_len == p->identity_len && + memcmp(p->identity, identity, identity_len) == 0) + break; + p = p->next; + } + + return p; +} + + +static struct eap_sim_reauth * +eap_sim_db_get_reauth(struct eap_sim_db_data *data, const u8 *identity, + size_t identity_len) +{ + char *reauth_id; + size_t len; + struct eap_sim_reauth *r; + + if (identity_len == 0 || + (identity[0] != EAP_SIM_REAUTH_ID_PREFIX && + identity[0] != EAP_AKA_REAUTH_ID_PREFIX)) + return NULL; + + /* Remove possible realm from identity */ + len = 0; + while (len < identity_len) { + if (identity[len] == '@') + break; + len++; + } + + reauth_id = malloc(len + 1); + if (reauth_id == NULL) + return NULL; + memcpy(reauth_id, identity, len); + reauth_id[len] = '\0'; + + r = data->reauths; + while (r) { + if (strcmp(r->reauth_id, reauth_id) == 0) + break; + r = r->next; + } + + free(reauth_id); + + return r; +} + + +static struct eap_sim_reauth * +eap_sim_db_get_reauth_id(struct eap_sim_db_data *data, const u8 *identity, + size_t identity_len) +{ + struct eap_sim_pseudonym *p; + struct eap_sim_reauth *r; + + if (identity_len == 0) + return NULL; + + p = eap_sim_db_get_pseudonym(data, identity, identity_len); + if (p == NULL) + p = eap_sim_db_get_pseudonym_id(data, identity, identity_len); + if (p) { + identity = p->identity; + identity_len = p->identity_len; + } + + r = data->reauths; + while (r) { + if (identity_len == r->identity_len && + memcmp(r->identity, identity, identity_len) == 0) + break; + r = r->next; + } + + return r; +} + + +/** + * eap_sim_db_identity_known - Verify whether the given identity is known + * @priv: Private data pointer from eap_sim_db_init() + * @identity: User name identity + * @identity_len: Length of identity in bytes + * Returns: 0 if the user is found or -1 on failure + * + * In most cases, the user name is ['0','1'] | IMSI, i.e., 1 followed by the + * IMSI in ASCII format, ['2','3'] | pseudonym, or ['4','5'] | reauth_id. + */ int eap_sim_db_identity_known(void *priv, const u8 *identity, size_t identity_len) { struct eap_sim_db_data *data = priv; - FILE *f; - char buf[80], *pos; - int i; - if (identity_len < 1 || identity[0] != '1') { + if (identity == NULL || identity_len < 2) + return -1; + + if (identity[0] == EAP_SIM_PSEUDONYM_PREFIX || + identity[0] == EAP_AKA_PSEUDONYM_PREFIX) { + struct eap_sim_pseudonym *p = + eap_sim_db_get_pseudonym(data, identity, identity_len); + return p ? 0 : -1; + } + + if (identity[0] == EAP_SIM_REAUTH_ID_PREFIX || + identity[0] == EAP_AKA_REAUTH_ID_PREFIX) { + struct eap_sim_reauth *r = + eap_sim_db_get_reauth(data, identity, identity_len); + return r ? 0 : -1; + } + + if (identity[0] != EAP_SIM_PERMANENT_PREFIX && + identity[0] != EAP_AKA_PERMANENT_PREFIX) { + /* Unknown identity prefix */ + return -1; + } + + /* TODO: Should consider asking HLR/AuC gateway whether this permanent + * identity is known. If it is, EAP-SIM/AKA can skip identity request. + * In case of EAP-AKA, this would reduce number of needed round-trips. + * Ideally, this would be done with one wait, i.e., just request + * authentication data and store it for the next use. This would then + * need to use similar pending-request functionality as the normal + * request for authentication data at later phase. + */ + return -1; +} + + +static char * eap_sim_db_get_next(struct eap_sim_db_data *data, char prefix) +{ + char *id, *pos, *end; + u8 buf[10]; + + if (hostapd_get_rand(buf, sizeof(buf))) + return NULL; + id = malloc(sizeof(buf) * 2 + 2); + if (id == NULL) + return NULL; + + pos = id; + end = id + sizeof(buf) * 2 + 2; + *pos++ = prefix; + pos += wpa_snprintf_hex(pos, end - pos, buf, sizeof(buf)); + + return id; +} + + +/** + * eap_sim_db_get_next_pseudonym - EAP-SIM DB: Get next pseudonym + * @priv: Private data pointer from eap_sim_db_init() + * @aka: Using EAP-AKA instead of EAP-SIM + * Returns: Next pseudonym (allocated string) or %NULL on failure + * + * This function is used to generate a pseudonym for EAP-SIM. The returned + * pseudonym is not added to database at this point; it will need to be added + * with eap_sim_db_add_pseudonym() once the authentication has been completed + * successfully. Caller is responsible for freeing the returned buffer. + */ +char * eap_sim_db_get_next_pseudonym(void *priv, int aka) +{ + struct eap_sim_db_data *data = priv; + return eap_sim_db_get_next(data, aka ? EAP_AKA_PSEUDONYM_PREFIX : + EAP_SIM_PSEUDONYM_PREFIX); +} + + +/** + * eap_sim_db_get_next_reauth_id - EAP-SIM DB: Get next reauth_id + * @priv: Private data pointer from eap_sim_db_init() + * @aka: Using EAP-AKA instead of EAP-SIM + * Returns: Next reauth_id (allocated string) or %NULL on failure + * + * This function is used to generate a fast re-authentication identity for + * EAP-SIM. The returned reauth_id is not added to database at this point; it + * will need to be added with eap_sim_db_add_reauth() once the authentication + * has been completed successfully. Caller is responsible for freeing the + * returned buffer. + */ +char * eap_sim_db_get_next_reauth_id(void *priv, int aka) +{ + struct eap_sim_db_data *data = priv; + return eap_sim_db_get_next(data, aka ? EAP_AKA_REAUTH_ID_PREFIX : + EAP_SIM_REAUTH_ID_PREFIX); +} + + +/** + * eap_sim_db_add_pseudonym - EAP-SIM DB: Add new pseudonym + * @priv: Private data pointer from eap_sim_db_init() + * @identity: Identity of the user (may be permanent identity or pseudonym) + * @identity_len: Length of identity + * @pseudonym: Pseudonym for this user. This needs to be an allocated buffer, + * e.g., return value from eap_sim_db_get_next_pseudonym(). Caller must not + * free it. + * Returns: 0 on success, -1 on failure + * + * This function adds a new pseudonym for EAP-SIM user. EAP-SIM DB is + * responsible of freeing pseudonym buffer once it is not needed anymore. + */ +int eap_sim_db_add_pseudonym(void *priv, const u8 *identity, + size_t identity_len, char *pseudonym) +{ + struct eap_sim_db_data *data = priv; + struct eap_sim_pseudonym *p; + wpa_hexdump_ascii(MSG_DEBUG, "EAP-SIM DB: Add pseudonym for identity", + identity, identity_len); + wpa_printf(MSG_DEBUG, "EAP-SIM DB: Pseudonym: %s", pseudonym); + + /* TODO: could store last two pseudonyms */ + p = eap_sim_db_get_pseudonym(data, identity, identity_len); + if (p == NULL) + p = eap_sim_db_get_pseudonym_id(data, identity, identity_len); + + if (p) { + wpa_printf(MSG_DEBUG, "EAP-SIM DB: Replacing previous " + "pseudonym: %s", p->pseudonym); + free(p->pseudonym); + p->pseudonym = pseudonym; + return 0; + } + + p = wpa_zalloc(sizeof(*p)); + if (p == NULL) { + free(pseudonym); return -1; } - f = fopen(data->fname, "r"); - if (f == NULL) { - wpa_printf(MSG_DEBUG, "EAP-SIM DB: could not open triplet " - "file '%s'", data->fname); + p->next = data->pseudonyms; + p->identity = malloc(identity_len); + if (p->identity == NULL) { + free(p); + free(pseudonym); return -1; } + memcpy(p->identity, identity, identity_len); + p->identity_len = identity_len; + p->pseudonym = pseudonym; + data->pseudonyms = p; + + wpa_printf(MSG_DEBUG, "EAP-SIM DB: Added new pseudonym entry"); + return 0; +} + + +/** + * eap_sim_db_add_reauth - EAP-SIM DB: Add new re-authentication entry + * @priv: Private data pointer from eap_sim_db_init() + * @identity: Identity of the user (may be permanent identity or pseudonym) + * @identity_len: Length of identity + * @reauth_id: reauth_id for this user. This needs to be an allocated buffer, + * e.g., return value from eap_sim_db_get_next_reauth_id(). Caller must not + * free it. + * @mk: 16-byte MK from the previous full authentication + * Returns: 0 on success, -1 on failure + * + * This function adds a new re-authentication entry for an EAP-SIM user. + * EAP-SIM DB is responsible of freeing reauth_id buffer once it is not needed + * anymore. + */ +int eap_sim_db_add_reauth(void *priv, const u8 *identity, + size_t identity_len, char *reauth_id, u16 counter, + const u8 *mk) +{ + struct eap_sim_db_data *data = priv; + struct eap_sim_reauth *r; + wpa_hexdump_ascii(MSG_DEBUG, "EAP-SIM DB: Add reauth_id for identity", + identity, identity_len); + wpa_printf(MSG_DEBUG, "EAP-SIM DB: reauth_id: %s", reauth_id); + + r = eap_sim_db_get_reauth(data, identity, identity_len); + if (r == NULL) + r = eap_sim_db_get_reauth_id(data, identity, identity_len); + + if (r) { + wpa_printf(MSG_DEBUG, "EAP-SIM DB: Replacing previous " + "reauth_id: %s", r->reauth_id); + free(r->reauth_id); + r->reauth_id = reauth_id; + } else { + r = wpa_zalloc(sizeof(*r)); + if (r == NULL) { + free(reauth_id); + return -1; + } + + r->next = data->reauths; + r->identity = malloc(identity_len); + if (r->identity == NULL) { + free(r); + free(reauth_id); + return -1; + } + memcpy(r->identity, identity, identity_len); + r->identity_len = identity_len; + r->reauth_id = reauth_id; + data->reauths = r; + wpa_printf(MSG_DEBUG, "EAP-SIM DB: Added new reauth entry"); + } + + r->counter = counter; + memcpy(r->mk, mk, EAP_SIM_MK_LEN); + + return 0; +} + + +/** + * eap_sim_db_get_permanent - EAP-SIM DB: Get permanent identity + * @priv: Private data pointer from eap_sim_db_init() + * @identity: Identity of the user (may be permanent identity or pseudonym) + * @identity_len: Length of identity + * @len: Buffer for length of the returned permanent identity + * Returns: Pointer to the permanent identity, or %NULL if not found + */ +const u8 * eap_sim_db_get_permanent(void *priv, const u8 *identity, + size_t identity_len, size_t *len) +{ + struct eap_sim_db_data *data = priv; + struct eap_sim_pseudonym *p; + + if (identity == NULL) + return NULL; + + p = eap_sim_db_get_pseudonym(data, identity, identity_len); + if (p == NULL) + p = eap_sim_db_get_pseudonym_id(data, identity, identity_len); + if (p == NULL) + return NULL; + + *len = p->identity_len; + return p->identity; +} + + +/** + * eap_sim_db_get_reauth_entry - EAP-SIM DB: Get re-authentication entry + * @priv: Private data pointer from eap_sim_db_init() + * @identity: Identity of the user (may be permanent identity, pseudonym, or + * reauth_id) + * @identity_len: Length of identity + * @len: Buffer for length of the returned permanent identity + * Returns: Pointer to the re-auth entry, or %NULL if not found + */ +struct eap_sim_reauth * +eap_sim_db_get_reauth_entry(void *priv, const u8 *identity, + size_t identity_len) +{ + struct eap_sim_db_data *data = priv; + struct eap_sim_reauth *r; + + if (identity == NULL) + return NULL; + r = eap_sim_db_get_reauth(data, identity, identity_len); + if (r == NULL) + r = eap_sim_db_get_reauth_id(data, identity, identity_len); + return r; +} + + +/** + * eap_sim_db_remove_reauth - EAP-SIM DB: Remove re-authentication entry + * @priv: Private data pointer from eap_sim_db_init() + * @reauth: Pointer to re-authentication entry from + * eap_sim_db_get_reauth_entry() + */ +void eap_sim_db_remove_reauth(void *priv, struct eap_sim_reauth *reauth) +{ + struct eap_sim_db_data *data = priv; + struct eap_sim_reauth *r, *prev = NULL; + r = data->reauths; + while (r) { + if (r == reauth) { + if (prev) + prev->next = r->next; + else + data->reauths = r->next; + eap_sim_db_free_reauth(r); + return; + } + prev = r; + r = r->next; + } +} + + +/** + * eap_sim_db_get_aka_auth - Get AKA authentication values + * @priv: Private data pointer from eap_sim_db_init() + * @identity: User name identity + * @identity_len: Length of identity in bytes + * @_rand: Buffer for RAND value + * @autn: Buffer for AUTN value + * @ik: Buffer for IK value + * @ck: Buffer for CK value + * @res: Buffer for RES value + * @res_len: Buffer for RES length + * @cb_session_ctx: Session callback context for get_complete_cb() + * Returns: 0 on success, -1 (EAP_SIM_DB_FAILURE) on error (e.g., user not + * found), or -2 (EAP_SIM_DB_PENDING) if results are not yet available. In this + * case, the callback function registered with eap_sim_db_init() will be + * called once the results become available. + * + * In most cases, the user name is '0' | IMSI, i.e., 0 followed by the IMSI in + * ASCII format. + * + * When using an external server for AKA authentication, this function can + * always start a request and return EAP_SIM_DB_PENDING immediately if + * authentication triplets are not available. Once the authentication data are + * received, callback function registered with eap_sim_db_init() is called to + * notify EAP state machine to reprocess the message. This + * eap_sim_db_get_aka_auth() function will then be called again and the newly + * received triplets will then be given to the caller. + */ +int eap_sim_db_get_aka_auth(void *priv, const u8 *identity, + size_t identity_len, u8 *_rand, u8 *autn, u8 *ik, + u8 *ck, u8 *res, size_t *res_len, + void *cb_session_ctx) +{ + struct eap_sim_db_data *data = priv; + struct eap_sim_db_pending *entry; + int len; + size_t i; + char msg[40]; - if (identity_len < 2 || identity[0] != '1') { + if (identity_len < 2 || identity == NULL || + identity[0] != EAP_AKA_PERMANENT_PREFIX || + identity_len + 1 > sizeof(entry->imsi)) { wpa_hexdump_ascii(MSG_DEBUG, "EAP-SIM DB: unexpected identity", identity, identity_len); - return -1; + return EAP_SIM_DB_FAILURE; } identity++; identity_len--; @@ -212,32 +1131,123 @@ int eap_sim_db_identity_known(void *priv, const u8 *identity, break; } } + wpa_hexdump_ascii(MSG_DEBUG, "EAP-SIM DB: Get AKA auth for IMSI", + identity, identity_len); - while (fgets(buf, sizeof(buf), f)) { - /* Parse IMSI:Kc:SRES:RAND and match IMSI with identity. */ - buf[sizeof(buf) - 1] = '\0'; - pos = buf; - while (*pos != '\0' && *pos != '\n') - pos++; - if (*pos == '\n') - *pos = '\0'; - if (pos - buf < 60 || pos[0] == '#') - continue; + entry = eap_sim_db_get_pending(data, identity, identity_len, 1); + if (entry) { + if (entry->state == FAILURE) { + free(entry); + wpa_printf(MSG_DEBUG, "EAP-SIM DB: Failure"); + return EAP_SIM_DB_FAILURE; + } - pos = strchr(buf, ':'); - if (pos == NULL) - continue; - *pos++ = '\0'; - if (strlen(buf) != identity_len || - memcmp(buf, identity, identity_len) != 0) - continue; + if (entry->state == PENDING) { + eap_sim_db_add_pending(data, entry); + wpa_printf(MSG_DEBUG, "EAP-SIM DB: Pending"); + return EAP_SIM_DB_PENDING; + } - fclose(f); + wpa_printf(MSG_DEBUG, "EAP-SIM DB: Returning successfully " + "received authentication data"); + memcpy(_rand, entry->u.aka.rand, EAP_AKA_RAND_LEN); + memcpy(autn, entry->u.aka.autn, EAP_AKA_AUTN_LEN); + memcpy(ik, entry->u.aka.ik, EAP_AKA_IK_LEN); + memcpy(ck, entry->u.aka.ck, EAP_AKA_CK_LEN); + memcpy(res, entry->u.aka.res, EAP_AKA_RES_MAX_LEN); + *res_len = entry->u.aka.res_len; + free(entry); return 0; } - /* IMSI not found */ + if (data->sock < 0) { + if (eap_sim_db_open_socket(data) < 0) + return EAP_SIM_DB_FAILURE; + } - fclose(f); - return -1; + len = snprintf(msg, sizeof(msg), "AKA-REQ-AUTH "); + if (len < 0 || len + identity_len >= sizeof(msg)) + return EAP_SIM_DB_FAILURE; + memcpy(msg + len, identity, identity_len); + len += identity_len; + + wpa_hexdump(MSG_DEBUG, "EAP-SIM DB: requesting AKA authentication " + "data for IMSI", identity, identity_len); + if (eap_sim_db_send(data, msg, len) < 0) + return EAP_SIM_DB_FAILURE; + + entry = wpa_zalloc(sizeof(*entry)); + if (entry == NULL) + return EAP_SIM_DB_FAILURE; + + os_get_time(&entry->timestamp); + entry->aka = 1; + memcpy(entry->imsi, identity, identity_len); + entry->imsi_len = identity_len; + entry->cb_session_ctx = cb_session_ctx; + entry->state = PENDING; + eap_sim_db_add_pending(data, entry); + eap_sim_db_expire_pending(data); + + return EAP_SIM_DB_PENDING; +} + + +/** + * eap_sim_db_resynchronize - Resynchronize AKA AUTN + * @priv: Private data pointer from eap_sim_db_init() + * @identity: User name identity + * @identity_len: Length of identity in bytes + * @auts: AUTS value from the peer + * @_rand: RAND value used in the rejected message + * Returns: 0 on success, -1 on failure + * + * This function is called when the peer reports synchronization failure in the + * AUTN value by sending AUTS. The AUTS and RAND values should be sent to + * HLR/AuC to allow it to resynchronize with the peer. After this, + * eap_sim_db_get_aka_auth() will be called again to to fetch updated + * RAND/AUTN values for the next challenge. + */ +int eap_sim_db_resynchronize(void *priv, const u8 *identity, + size_t identity_len, const u8 *auts, + const u8 *_rand) +{ + struct eap_sim_db_data *data = priv; + + if (identity_len < 2 || identity[0] != EAP_AKA_PERMANENT_PREFIX || + identity_len > 20) { + wpa_hexdump_ascii(MSG_DEBUG, "EAP-SIM DB: unexpected identity", + identity, identity_len); + return -1; + } + + if (data->sock >= 0) { + char msg[100]; + int len, ret; + + len = snprintf(msg, sizeof(msg), "AKA-AUTS "); + if (len < 0 || len + identity_len - 1 >= sizeof(msg)) + return -1; + memcpy(msg + len, identity + 1, identity_len - 1); + len += identity_len - 1; + + ret = snprintf(msg + len, sizeof(msg) - len, " "); + if (ret < 0 || (size_t) ret >= sizeof(msg) - len) + return -1; + len += ret; + len += wpa_snprintf_hex(msg + len, sizeof(msg) - len, + auts, EAP_AKA_AUTS_LEN); + ret = snprintf(msg + len, sizeof(msg) - len, " "); + if (ret < 0 || (size_t) ret >= sizeof(msg) - len) + return -1; + len += ret; + len += wpa_snprintf_hex(msg + len, sizeof(msg) - len, + _rand, EAP_AKA_RAND_LEN); + wpa_hexdump(MSG_DEBUG, "EAP-SIM DB: reporting AKA AUTS for " + "IMSI", identity + 1, identity_len - 1); + if (eap_sim_db_send(data, msg, len) < 0) + return -1; + } + + return 0; } |