diff --git a/hostapd/Makefile b/hostapd/Makefile index 7bd080f54..420bc5b67 100644 --- a/hostapd/Makefile +++ b/hostapd/Makefile @@ -884,6 +884,11 @@ ifdef TLS_FUNCS LIBS_n += -lcrypto endif +ifdef CONFIG_SQLITE +CFLAGS += -DCONFIG_SQLITE +LIBS_h += -lsqlite3 +endif + HOBJS += hlr_auc_gw.o ../src/utils/common.o ../src/utils/wpa_debug.o ../src/utils/os_$(CONFIG_OS).o ../src/utils/wpabuf.o ../src/crypto/milenage.o HOBJS += ../src/crypto/aes-encblock.o ifdef CONFIG_INTERNAL_AES diff --git a/hostapd/defconfig b/hostapd/defconfig index 3419a18cf..204aa7683 100644 --- a/hostapd/defconfig +++ b/hostapd/defconfig @@ -264,3 +264,6 @@ CONFIG_IPV6=y # Hotspot 2.0 #CONFIG_HS20=y + +# Enable SQLite database support in hlr_auc_gw +#CONFIG_SQLITE=y diff --git a/hostapd/hlr_auc_gw.c b/hostapd/hlr_auc_gw.c index 02a6a59fd..e27ddaba9 100644 --- a/hostapd/hlr_auc_gw.c +++ b/hostapd/hlr_auc_gw.c @@ -43,6 +43,9 @@ #include "includes.h" #include +#ifdef CONFIG_SQLITE +#include +#endif /* CONFIG_SQLITE */ #include "common.h" #include "crypto/milenage.h" @@ -89,6 +92,140 @@ static struct milenage_parameters *milenage_db = NULL; #define EAP_AKA_CK_LEN 16 +#ifdef CONFIG_SQLITE + +static sqlite3 *sqlite_db = NULL; +static struct milenage_parameters db_tmp_milenage; + + +static int db_table_exists(sqlite3 *db, const char *name) +{ + char cmd[128]; + os_snprintf(cmd, sizeof(cmd), "SELECT 1 FROM %s;", name); + return sqlite3_exec(db, cmd, NULL, NULL, NULL) == SQLITE_OK; +} + + +static int db_table_create_milenage(sqlite3 *db) +{ + char *err = NULL; + const char *sql = + "CREATE TABLE milenage(" + " imsi INTEGER PRIMARY KEY NOT NULL," + " ki CHAR(32) NOT NULL," + " opc CHAR(32) NOT NULL," + " amf CHAR(4) NOT NULL," + " sqn CHAR(12) NOT NULL" + ");"; + + printf("Adding database table for milenage information\n"); + if (sqlite3_exec(db, sql, NULL, NULL, &err) != SQLITE_OK) { + printf("SQLite error: %s\n", err); + sqlite3_free(err); + return -1; + } + + return 0; +} + + +static sqlite3 * db_open(const char *db_file) +{ + sqlite3 *db; + + if (sqlite3_open(db_file, &db)) { + printf("Failed to open database %s: %s\n", + db_file, sqlite3_errmsg(db)); + sqlite3_close(db); + return NULL; + } + + if (!db_table_exists(db, "milenage") && + db_table_create_milenage(db) < 0) { + sqlite3_close(db); + return NULL; + } + + return db; +} + + +static int get_milenage_cb(void *ctx, int argc, char *argv[], char *col[]) +{ + struct milenage_parameters *m = ctx; + int i; + + for (i = 0; i < argc; i++) { + if (os_strcmp(col[i], "ki") == 0 && argv[i] && + hexstr2bin(argv[i], m->ki, sizeof(m->ki))) { + printf("Invalid ki value in database\n"); + return -1; + } + + if (os_strcmp(col[i], "opc") == 0 && argv[i] && + hexstr2bin(argv[i], m->opc, sizeof(m->opc))) { + printf("Invalid opcvalue in database\n"); + return -1; + } + + if (os_strcmp(col[i], "amf") == 0 && argv[i] && + hexstr2bin(argv[i], m->amf, sizeof(m->amf))) { + printf("Invalid amf value in database\n"); + return -1; + } + + if (os_strcmp(col[i], "sqn") == 0 && argv[i] && + hexstr2bin(argv[i], m->sqn, sizeof(m->sqn))) { + printf("Invalid sqn value in database\n"); + return -1; + } + } + + return 0; +} + + +static struct milenage_parameters * db_get_milenage(const char *imsi_txt) +{ + char cmd[128]; + unsigned long long imsi; + + os_memset(&db_tmp_milenage, 0, sizeof(db_tmp_milenage)); + imsi = atoll(imsi_txt); + os_snprintf(db_tmp_milenage.imsi, sizeof(db_tmp_milenage.imsi), + "%llu", imsi); + os_snprintf(cmd, sizeof(cmd), + "SELECT ki,opc,amf,sqn FROM milenage WHERE imsi=%llu;", + imsi); + if (sqlite3_exec(sqlite_db, cmd, get_milenage_cb, &db_tmp_milenage, + NULL) != SQLITE_OK) + return NULL; + + return &db_tmp_milenage; +} + + +static int db_update_milenage_sqn(struct milenage_parameters *m) +{ + char cmd[128], val[13], *pos; + + pos = val; + pos += wpa_snprintf_hex(pos, sizeof(val), m->sqn, 6); + *pos = '\0'; + os_snprintf(cmd, sizeof(cmd), + "UPDATE milenage SET sqn='%s' WHERE imsi=%s;", + val, m->imsi); + if (sqlite3_exec(sqlite_db, cmd, NULL, NULL, NULL) != SQLITE_OK) { + printf("Failed to update SQN in database for IMSI %s\n", + m->imsi); + return -1; + } + return 0; +} + +#endif /* CONFIG_SQLITE */ + + static int open_socket(const char *path) { struct sockaddr_un addr; @@ -460,6 +597,11 @@ static struct milenage_parameters * get_milenage(const char *imsi) m = m->next; } +#ifdef CONFIG_SQLITE + if (!m) + m = db_get_milenage(imsi); +#endif /* CONFIG_SQLITE */ + return m; } @@ -585,6 +727,9 @@ static void aka_req_auth(int s, struct sockaddr_un *from, socklen_t fromlen, return; res_len = EAP_AKA_RES_MAX_LEN; inc_sqn(m->sqn); +#ifdef CONFIG_SQLITE + db_update_milenage_sqn(m); +#endif /* CONFIG_SQLITE */ sqn_changes = 1; printf("AKA: Milenage with SQN=%02x%02x%02x%02x%02x%02x\n", m->sqn[0], m->sqn[1], m->sqn[2], @@ -677,6 +822,9 @@ static void aka_auts(int s, struct sockaddr_un *from, socklen_t fromlen, printf("AKA-AUTS: Re-synchronized: " "SQN=%02x%02x%02x%02x%02x%02x\n", sqn[0], sqn[1], sqn[2], sqn[3], sqn[4], sqn[5]); +#ifdef CONFIG_SQLITE + db_update_milenage_sqn(m); +#endif /* CONFIG_SQLITE */ sqn_changes = 1; } } @@ -743,6 +891,13 @@ static void cleanup(void) close(serv_sock); unlink(socket_path); + +#ifdef CONFIG_SQLITE + if (sqlite_db) { + sqlite3_close(sqlite_db); + sqlite_db = NULL; + } +#endif /* CONFIG_SQLITE */ } @@ -762,7 +917,7 @@ static void usage(void) "usage:\n" "hlr_auc_gw [-hu] [-s] [-g] " "[-m] \\\n" - " [-i]\n" + " [-D] [-i]\n" "\n" "options:\n" " -h = show this usage help\n" @@ -771,6 +926,7 @@ static void usage(void) " (default: %s)\n" " -g = path for GSM authentication triplets\n" " -m = path for Milenage keys\n" + " -D = path to SQLite database\n" " -i = IND length for SQN (default: 5)\n", default_socket_path); } @@ -780,6 +936,7 @@ int main(int argc, char *argv[]) { int c; char *gsm_triplet_file = NULL; + char *sqlite_db_file = NULL; if (os_program_init()) return -1; @@ -787,10 +944,18 @@ int main(int argc, char *argv[]) socket_path = default_socket_path; for (;;) { - c = getopt(argc, argv, "g:hi:m:s:u"); + c = getopt(argc, argv, "D:g:hi:m:s:u"); if (c < 0) break; switch (c) { + case 'D': +#ifdef CONFIG_SQLITE + sqlite_db_file = optarg; + break; +#else /* CONFIG_SQLITE */ + printf("No SQLite support included in the build\n"); + return -1; +#endif /* CONFIG_SQLITE */ case 'g': gsm_triplet_file = optarg; break; @@ -819,6 +984,16 @@ int main(int argc, char *argv[]) } } + if (!gsm_triplet_file && !milenage_file && !sqlite_db_file) { + usage(); + return -1; + } + +#ifdef CONFIG_SQLITE + if (sqlite_db_file && (sqlite_db = db_open(sqlite_db_file)) == NULL) + return -1; +#endif /* CONFIG_SQLITE */ + if (gsm_triplet_file && read_gsm_triplets(gsm_triplet_file) < 0) return -1; @@ -838,6 +1013,13 @@ int main(int argc, char *argv[]) for (;;) process(serv_sock); +#ifdef CONFIG_SQLITE + if (sqlite_db) { + sqlite3_close(sqlite_db); + sqlite_db = NULL; + } +#endif /* CONFIG_SQLITE */ + os_program_deinit(); return 0; diff --git a/hostapd/hlr_auc_gw.txt b/hostapd/hlr_auc_gw.txt new file mode 100644 index 000000000..e4b678315 --- /dev/null +++ b/hostapd/hlr_auc_gw.txt @@ -0,0 +1,98 @@ +HLR/AuC testing gateway for hostapd EAP-SIM/AKA database/authenticator + +hlr_auc_gw is an example implementation of the EAP-SIM/AKA/AKA' +database/authentication gateway interface to HLR/AuC. It could be +replaced with an implementation of SS7 gateway to GSM/UMTS +authentication center (HLR/AuC). hostapd will send SIM/AKA +authentication queries over a UNIX domain socket to and external +program, e.g., hlr_auc_gw. + +hlr_auc_gw can be configured with GSM and UMTS authentication data with +text files: GSM triplet file (see hostapd.sim_db) and Milenage file (see +hlr_auc_gw.milenage_db). Milenage parameters can be used to generate +dynamic authentication data for EAP-SIM, EAP-AKA, and EAP-AKA' while the +GSM triplet data is used for a more static configuration (e.g., triplets +extracted from a SIM card). + +Alternatively, hlr_auc_gw can be built with support for an SQLite +database for more dynamic operations. This is enabled by adding +"CONFIG_SQLITE=y" into hostapd/.config before building hlr_auc_gw ("make +clean; make hlr_auc_gw" in this directory). + +hostapd is configured to use hlr_auc_gw with the eap_sim_db parameter in +hostapd.conf (e.g., "eap_sim_db=unix:/tmp/hlr_auc_gw.sock"). hlr_auc_gw +is configured with command line parameters: + +hlr_auc_gw [-hu] [-s] [-g] [-m] \ + [-D] [-i] + +options: + -h = show this usage help + -u = update SQN in Milenage file on exit + -s = path for UNIX domain socket + (default: /tmp/hlr_auc_gw.sock) + -g = path for GSM authentication triplets + -m = path for Milenage keys + -D = path to SQLite database + -i = IND length for SQN (default: 5) + + +The SQLite database can be initialized with sqlite, e.g., by running +following commands in "sqlite3 /path/to/hlr_auc_gw.db": + +CREATE TABLE milenage( + imsi INTEGER PRIMARY KEY NOT NULL, + ki CHAR(32) NOT NULL, + opc CHAR(32) NOT NULL, + amf CHAR(4) NOT NULL, + sqn CHAR(12) NOT NULL +); +INSERT INTO milenage(imsi,ki,opc,amf,sqn) VALUES( + 232010000000000, + '90dca4eda45b53cf0f12d7c9c3bc6a89', + 'cb9cccc4b9258e6dca4760379fb82581', + '61df', + '000000000000' +); +INSERT INTO milenage(imsi,ki,opc,amf,sqn) VALUES( + 555444333222111, + '5122250214c33e723a5dd523fc145fc0', + '981d464c7c52eb6e5036234984ad0bcf', + 'c3ab', + '16f3b3f70fc1' +); + + +"hlr_auc_gw -D /path/to/hlr_auc_gw.db" can then be used to fetch +Milenage parameters based on IMSI from the database. The database can be +updated dynamically while hlr_auc_gw is running to add/remove/modify +entries. + + +Example configuration files for hostapd to operate as a RADIUS +authentication server for EAP-SIM/AKA/AKA': + +hostapd.conf: + +driver=none +radius_server_clients=hostapd.radius_clients +eap_server=1 +eap_user_file=hostapd.eap_user +eap_sim_db=unix:/tmp/hlr_auc_gw.sock +eap_sim_aka_result_ind=1 + +hostapd.radius_clients: + +0.0.0.0/0 radius + +hostapd.eap_user: + +"0"* AKA +"1"* SIM +"2"* AKA +"3"* SIM +"4"* AKA +"5"* SIM +"6"* AKA' +"7"* AKA' +"8"* AKA'