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:
parent
e43bd7a1b7
commit
597d5d1b11
4 changed files with 290 additions and 2 deletions
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
98
hostapd/hlr_auc_gw.txt
Normal 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'
|
Loading…
Reference in a new issue