hlr_auc_gw: Add SQLite database support for Milenage information

An SQLite database can now be used to manage the Milenage information
instead of a text file. The new hlr_auc_gw.txt document describes how
this is configured and used.

Signed-hostap: Jouni Malinen <j@w1.fi>
This commit is contained in:
Jouni Malinen 2012-08-19 22:59:30 +03:00
parent e43bd7a1b7
commit 597d5d1b11
4 changed files with 290 additions and 2 deletions

View file

@ -884,6 +884,11 @@ ifdef TLS_FUNCS
LIBS_n += -lcrypto LIBS_n += -lcrypto
endif 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 += 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 HOBJS += ../src/crypto/aes-encblock.o
ifdef CONFIG_INTERNAL_AES ifdef CONFIG_INTERNAL_AES

View file

@ -264,3 +264,6 @@ CONFIG_IPV6=y
# Hotspot 2.0 # Hotspot 2.0
#CONFIG_HS20=y #CONFIG_HS20=y
# Enable SQLite database support in hlr_auc_gw
#CONFIG_SQLITE=y

View file

@ -43,6 +43,9 @@
#include "includes.h" #include "includes.h"
#include <sys/un.h> #include <sys/un.h>
#ifdef CONFIG_SQLITE
#include <sqlite3.h>
#endif /* CONFIG_SQLITE */
#include "common.h" #include "common.h"
#include "crypto/milenage.h" #include "crypto/milenage.h"
@ -89,6 +92,140 @@ static struct milenage_parameters *milenage_db = NULL;
#define EAP_AKA_CK_LEN 16 #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) static int open_socket(const char *path)
{ {
struct sockaddr_un addr; struct sockaddr_un addr;
@ -460,6 +597,11 @@ static struct milenage_parameters * get_milenage(const char *imsi)
m = m->next; m = m->next;
} }
#ifdef CONFIG_SQLITE
if (!m)
m = db_get_milenage(imsi);
#endif /* CONFIG_SQLITE */
return m; return m;
} }
@ -585,6 +727,9 @@ static void aka_req_auth(int s, struct sockaddr_un *from, socklen_t fromlen,
return; return;
res_len = EAP_AKA_RES_MAX_LEN; res_len = EAP_AKA_RES_MAX_LEN;
inc_sqn(m->sqn); inc_sqn(m->sqn);
#ifdef CONFIG_SQLITE
db_update_milenage_sqn(m);
#endif /* CONFIG_SQLITE */
sqn_changes = 1; sqn_changes = 1;
printf("AKA: Milenage with SQN=%02x%02x%02x%02x%02x%02x\n", printf("AKA: Milenage with SQN=%02x%02x%02x%02x%02x%02x\n",
m->sqn[0], m->sqn[1], m->sqn[2], 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: " printf("AKA-AUTS: Re-synchronized: "
"SQN=%02x%02x%02x%02x%02x%02x\n", "SQN=%02x%02x%02x%02x%02x%02x\n",
sqn[0], sqn[1], sqn[2], sqn[3], sqn[4], sqn[5]); 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; sqn_changes = 1;
} }
} }
@ -743,6 +891,13 @@ static void cleanup(void)
close(serv_sock); close(serv_sock);
unlink(socket_path); 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" "usage:\n"
"hlr_auc_gw [-hu] [-s<socket path>] [-g<triplet file>] " "hlr_auc_gw [-hu] [-s<socket path>] [-g<triplet file>] "
"[-m<milenage file>] \\\n" "[-m<milenage file>] \\\n"
" [-i<IND len in bits>]\n" " [-D<DB file>] [-i<IND len in bits>]\n"
"\n" "\n"
"options:\n" "options:\n"
" -h = show this usage help\n" " -h = show this usage help\n"
@ -771,6 +926,7 @@ static void usage(void)
" (default: %s)\n" " (default: %s)\n"
" -g<triplet file> = path for GSM authentication triplets\n" " -g<triplet file> = path for GSM authentication triplets\n"
" -m<milenage file> = path for Milenage keys\n" " -m<milenage file> = path for Milenage keys\n"
" -D<DB file> = path to SQLite database\n"
" -i<IND len in bits> = IND length for SQN (default: 5)\n", " -i<IND len in bits> = IND length for SQN (default: 5)\n",
default_socket_path); default_socket_path);
} }
@ -780,6 +936,7 @@ int main(int argc, char *argv[])
{ {
int c; int c;
char *gsm_triplet_file = NULL; char *gsm_triplet_file = NULL;
char *sqlite_db_file = NULL;
if (os_program_init()) if (os_program_init())
return -1; return -1;
@ -787,10 +944,18 @@ int main(int argc, char *argv[])
socket_path = default_socket_path; socket_path = default_socket_path;
for (;;) { for (;;) {
c = getopt(argc, argv, "g:hi:m:s:u"); c = getopt(argc, argv, "D:g:hi:m:s:u");
if (c < 0) if (c < 0)
break; break;
switch (c) { 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': case 'g':
gsm_triplet_file = optarg; gsm_triplet_file = optarg;
break; 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) if (gsm_triplet_file && read_gsm_triplets(gsm_triplet_file) < 0)
return -1; return -1;
@ -838,6 +1013,13 @@ int main(int argc, char *argv[])
for (;;) for (;;)
process(serv_sock); process(serv_sock);
#ifdef CONFIG_SQLITE
if (sqlite_db) {
sqlite3_close(sqlite_db);
sqlite_db = NULL;
}
#endif /* CONFIG_SQLITE */
os_program_deinit(); os_program_deinit();
return 0; return 0;

98
hostapd/hlr_auc_gw.txt Normal file
View file

@ -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<socket path>] [-g<triplet file>] [-m<milenage file>] \
[-D<DB file>] [-i<IND len in bits>]
options:
-h = show this usage help
-u = update SQN in Milenage file on exit
-s<socket path> = path for UNIX domain socket
(default: /tmp/hlr_auc_gw.sock)
-g<triplet file> = path for GSM authentication triplets
-m<milenage file> = path for Milenage keys
-D<DB file> = path to SQLite database
-i<IND len in bits> = 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'