VTY: alloc/dealloc/show imsi pseudo

Create VTY commands and related database functions to get, allocate and
deallocate pseudonymous IMSIs. As this is a proof of concept, the code
to retrieve the next pseudonymous IMSI has no performance optimizations
and might not use a good entropy source (I did not verify).

Related: OS#4476
Change-Id: Ia93ee58d5e03c801eb774b9483a9a4417d031959
This commit is contained in:
Oliver Smith 2020-04-01 13:13:50 +02:00
parent 502f10fb0c
commit 7b4e5b1390
13 changed files with 345 additions and 0 deletions

View File

@ -9,6 +9,7 @@ noinst_HEADERS = \
hlr_ussd.h \
hlr_vty.h \
hlr_vty_subscr.h \
imsi_pseudo.h \
logging.h \
lu_fsm.h \
mslookup_server.h \

View File

@ -33,6 +33,10 @@ enum stmt_idx {
DB_STMT_SET_LAST_LU_SEEN_PS,
DB_STMT_EXISTS_BY_IMSI,
DB_STMT_EXISTS_BY_MSISDN,
DB_STMT_PSEUDO_BY_ID,
DB_STMT_PSEUDO_INSERT,
DB_STMT_PSEUDO_DELETE,
DB_STMT_PSEUDO_NEXT,
_NUM_DB_STMT
};

View File

@ -0,0 +1,19 @@
#pragma once
#include <stdint.h>
#include <inttypes.h>
#include <osmocom/hlr/db.h>
#define LOGPSEUDO(id, level, fmt, args ...) LOGP(DPSEUDO, level, "subscriber_id='%" PRId64 "': " fmt, id, ## args)
struct imsi_pseudo_data {
int alloc_count; /* 0: none, 1: only current is allocated, 2: current and previous are allocated */
int64_t i; /* current imsi_pseudo_i */
char current[GSM23003_IMSI_MAX_DIGITS+1];
char previous[GSM23003_IMSI_MAX_DIGITS+1];
};
int db_get_imsi_pseudo_data(struct db_context *dbc, int64_t subscr_id, struct imsi_pseudo_data *data);
int db_alloc_imsi_pseudo(struct db_context *dbc, int64_t subscr_id, const char *imsi_pseudo, int64_t imsi_pseudo_i);
int db_dealloc_imsi_pseudo(struct db_context *dbc, const char *imsi_pseudo);
int db_get_imsi_pseudo_next(struct db_context *dbc, char *imsi_pseudo);

View File

@ -8,6 +8,7 @@ enum {
DGSUP,
DAUC,
DSS,
DPSEUDO,
DMSLOOKUP,
DLU,
DDGSM,

View File

@ -44,6 +44,7 @@ osmo_hlr_SOURCES = \
db.c \
db_auc.c \
db_hlr.c \
db_imsi_pseudo.c \
gsup_router.c \
gsup_server.c \
hlr.c \

View File

@ -85,6 +85,23 @@ static const char *stmt_sql[] = {
[DB_STMT_SET_LAST_LU_SEEN_PS] = "UPDATE subscriber SET last_lu_seen_ps = datetime($val, 'unixepoch') WHERE id = $subscriber_id",
[DB_STMT_EXISTS_BY_IMSI] = "SELECT 1 FROM subscriber WHERE imsi = $imsi",
[DB_STMT_EXISTS_BY_MSISDN] = "SELECT 1 FROM subscriber WHERE msisdn = $msisdn",
[DB_STMT_PSEUDO_BY_ID] =
"SELECT imsi_pseudo, imsi_pseudo_i"
" FROM subscriber_imsi_pseudo"
" WHERE subscriber_id = $subscriber_id"
" ORDER BY imsi_pseudo_i DESC",
[DB_STMT_PSEUDO_INSERT] =
"INSERT INTO subscriber_imsi_pseudo (subscriber_id, imsi_pseudo, imsi_pseudo_i)"
" VALUES($subscriber_id, $imsi_pseudo, $imsi_pseudo_i)",
[DB_STMT_PSEUDO_DELETE] =
"DELETE FROM subscriber_imsi_pseudo WHERE imsi_pseudo = $imsi_pseudo",
[DB_STMT_PSEUDO_NEXT] = /* Proof of concept! Verify performance and entropy usage before using in real world! */
"SELECT imsi"
" FROM subscriber"
" LEFT JOIN subscriber_imsi_pseudo ON imsi = imsi_pseudo"
" WHERE imsi_pseudo IS NULL"
" ORDER BY RANDOM()"
" LIMIT 1",
};
static void sql3_error_log_cb(void *arg, int err_code, const char *msg)

143
src/db_imsi_pseudo.c Normal file
View File

@ -0,0 +1,143 @@
/* (C) 2020 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <errno.h>
#include <inttypes.h>
#include <sqlite3.h>
#include <osmocom/hlr/logging.h>
#include <osmocom/hlr/db.h>
#include <osmocom/hlr/imsi_pseudo.h>
int db_get_imsi_pseudo_data(struct db_context *dbc, int64_t subscr_id, struct imsi_pseudo_data *data)
{
sqlite3_stmt *stmt = dbc->stmt[DB_STMT_PSEUDO_BY_ID];
int i, rc, ret = 0;
memset(data, 0, sizeof(*data));
if (!db_bind_int64(stmt, "$subscriber_id", subscr_id))
return -EIO;
/* Retrieve up to two allocated pseudo IMSIs in three sqlite3 steps */
for (i = 0; i < 3; i++) {
rc = sqlite3_step(stmt);
switch (rc) {
case SQLITE_ROW:
data->alloc_count = i + 1;
switch (i) {
case 0:
/* First entry is always current (ORDER BY in SQL statement) */
copy_sqlite3_text_to_buf(data->current, stmt, 0);
data->i = sqlite3_column_int(stmt, 1);
break;
case 1:
copy_sqlite3_text_to_buf(data->previous, stmt, 0);
break;
case 2:
LOGPSEUDO(subscr_id, LOGL_ERROR, "more than two pseudonymous IMSI allocated\n");
ret = -EINVAL;
goto out;
}
break;
case SQLITE_DONE:
goto out;
default:
LOGPSEUDO(subscr_id, LOGL_ERROR, "error executing SQL: %d\n", rc);
ret = -EIO;
goto out;
}
}
out:
db_remove_reset(stmt);
return ret;
}
int db_alloc_imsi_pseudo(struct db_context *dbc, int64_t subscr_id, const char *imsi_pseudo, int64_t imsi_pseudo_i)
{
sqlite3_stmt *stmt = dbc->stmt[DB_STMT_PSEUDO_INSERT];
int rc, ret = 0;
if (!db_bind_int64(stmt, "$subscriber_id", subscr_id))
return -EIO;
if (!db_bind_text(stmt, "$imsi_pseudo", imsi_pseudo))
return -EIO;
if (!db_bind_int64(stmt, "$imsi_pseudo_i", imsi_pseudo_i))
return -EIO;
rc = sqlite3_step(stmt);
if (rc != SQLITE_DONE) {
LOGPSEUDO(subscr_id, LOGL_ERROR, "imsi_pseudo='%s', imsi_pseudo_i='%" PRId64 "': SQL error during"
" allocate: %d\n", imsi_pseudo, imsi_pseudo_i, rc);
ret = -EIO;
}
db_remove_reset(stmt);
return ret;
}
int db_dealloc_imsi_pseudo(struct db_context *dbc, const char *imsi_pseudo)
{
sqlite3_stmt *stmt = dbc->stmt[DB_STMT_PSEUDO_DELETE];
int rc, ret = 0;
if (!db_bind_text(stmt, "$imsi_pseudo", imsi_pseudo))
return -EIO;
rc = sqlite3_step(stmt);
if (rc != SQLITE_DONE) {
LOGP(DPSEUDO, LOGL_ERROR, "imsi_pseudo='%s': SQL error during deallocate: %d\n", imsi_pseudo, rc);
ret = -EIO;
}
db_remove_reset(stmt);
return ret;
}
/*! Get the next random free pseudo IMSI.
* \param[in] dbc database context.
* \param[out] imsi_pseudo buffer with length GSM23003_IMSI_MAX_DIGITS+1.
* \returns 0: success, -1: no next IMSI available, -2: SQL error. */
int db_get_imsi_pseudo_next(struct db_context *dbc, char *imsi_pseudo)
{
sqlite3_stmt *stmt = dbc->stmt[DB_STMT_PSEUDO_NEXT];
const char *imsi;
int rc, ret = 0;
rc = sqlite3_step(stmt);
switch (rc) {
case SQLITE_ROW:
/* Can't use copy_sqlite3_text_to_buf, as it assumes the wrong size for imsi_pseudo */
imsi = (const char *)sqlite3_column_text(stmt, 0);
osmo_strlcpy(imsi_pseudo, imsi, GSM23003_IMSI_MAX_DIGITS+1);
break;
case SQLITE_DONE:
LOGP(DPSEUDO, LOGL_ERROR, "failed to get next pseudonymous IMSI: all IMSIs are already allocated as"
" pseudo IMSI\n");
ret = -1;
break;
default:
LOGP(DPSEUDO, LOGL_ERROR, "failed to get next pseudonymous IMSI, SQL error: %d\n", rc);
ret = -2;
break;
}
db_remove_reset(stmt);
return ret;
}

View File

@ -31,6 +31,7 @@
#include <osmocom/hlr/hlr.h>
#include <osmocom/hlr/db.h>
#include <osmocom/hlr/timestamp.h>
#include <osmocom/hlr/imsi_pseudo.h>
struct vty;
@ -69,6 +70,18 @@ static void dump_last_lu_seen(struct vty *vty, const char *domain_label, time_t
}
}
static void dump_imsi_pseudo(struct vty *vty, struct hlr_subscriber *subscr)
{
struct imsi_pseudo_data data;
if (db_get_imsi_pseudo_data(g_hlr->dbc, subscr->id, &data) != 0)
return;
if (data.alloc_count >= 1)
vty_out(vty, " Pseudonymous IMSI (current): %s, i: %" PRId64 "%s", data.current, data.i, VTY_NEWLINE);
if (data.alloc_count == 2)
vty_out(vty, " Pseudonymous IMSI (previous): %s%s", data.previous, VTY_NEWLINE);
}
static void subscr_dump_full_vty(struct vty *vty, struct hlr_subscriber *subscr)
{
int rc;
@ -78,6 +91,7 @@ static void subscr_dump_full_vty(struct vty *vty, struct hlr_subscriber *subscr)
vty_out(vty, " ID: %"PRIu64"%s", subscr->id, VTY_NEWLINE);
vty_out(vty, " IMSI: %s%s", *subscr->imsi ? subscr->imsi : "none", VTY_NEWLINE);
dump_imsi_pseudo(vty, subscr);
vty_out(vty, " MSISDN: %s%s", *subscr->msisdn ? subscr->msisdn : "none", VTY_NEWLINE);
if (*subscr->imei) {
@ -203,6 +217,7 @@ static int get_subscr_by_argv(struct vty *vty, const char *type, const char *id,
#define SUBSCR_UPDATE SUBSCR "update "
#define SUBSCR_UPDATE_HELP SUBSCR_HELP "Set or update subscriber data\n"
#define SUBSCR_MSISDN_HELP "Set MSISDN (phone number) of the subscriber\n"
#define SUBSCR_IMSI_PSEUDO_HELP "Allocate or deallocate pseudonymous IMSI of the subscriber\n"
DEFUN(subscriber_show,
subscriber_show_cmd,
@ -625,6 +640,85 @@ DEFUN(subscriber_nam,
return CMD_SUCCESS;
}
DEFUN(subscriber_imsi_pseudo_alloc,
subscriber_imsi_pseudo_alloc_cmd,
SUBSCR_UPDATE "imsi-pseudo alloc",
SUBSCR_UPDATE_HELP
SUBSCR_IMSI_PSEUDO_HELP
"Allocate a new pseudonymous IMSI (max. 2)\n")
{
struct hlr_subscriber subscr;
struct imsi_pseudo_data pseudo;
const char *id_type = argv[0];
const char *id = argv[1];
char imsi_pseudo[GSM23003_IMSI_MAX_DIGITS+1];
int rc;
if (get_subscr_by_argv(vty, id_type, id, &subscr))
return CMD_WARNING;
if (db_get_imsi_pseudo_data(g_hlr->dbc, subscr.id, &pseudo) != 0)
return CMD_WARNING;
if (pseudo.alloc_count == 2) {
vty_out(vty, "%% Error: subscriber already has two pseudonymous IMSI allocated%s", VTY_NEWLINE);
return CMD_WARNING;
}
rc = db_get_imsi_pseudo_next(g_hlr->dbc, imsi_pseudo);
switch (rc) {
case -1:
vty_out(vty, "%% Error: all IMSIs are already allocated as pseudonymous IMSI%s", VTY_NEWLINE);
return CMD_WARNING;
case -2:
vty_out(vty, "%% Error: failed to get next pseudonymous IMSI (SQL error)%s", VTY_NEWLINE);
return CMD_WARNING;
}
if (db_alloc_imsi_pseudo(g_hlr->dbc, subscr.id, imsi_pseudo, pseudo.i + 1) != 0) {
vty_out(vty, "%% Error: failed to allocate pseudonymous IMSI '%s'%s", imsi_pseudo, VTY_NEWLINE);
return CMD_WARNING;
}
vty_out(vty, "%% New pseudonymous IMSI allocated: %s%s", imsi_pseudo, VTY_NEWLINE);
return CMD_SUCCESS;
}
DEFUN(subscriber_imsi_pseudo_dealloc,
subscriber_imsi_pseudo_dealloc_cmd,
SUBSCR_UPDATE "imsi-pseudo dealloc IMSI_PSEUDO",
SUBSCR_UPDATE_HELP
SUBSCR_IMSI_PSEUDO_HELP
"Deallocate a pseudonymous IMSI\n"
"Pseudonymous IMSI to deallocate\n")
{
struct hlr_subscriber subscr;
struct imsi_pseudo_data pseudo;
const char *id_type = argv[0];
const char *id = argv[1];
const char *imsi_pseudo = argv[2];
if (get_subscr_by_argv(vty, id_type, id, &subscr))
return CMD_WARNING;
if (db_get_imsi_pseudo_data(g_hlr->dbc, subscr.id, &pseudo) != 0)
return CMD_WARNING;
if ((pseudo.alloc_count >= 1 && strcmp(imsi_pseudo, pseudo.current) == 0) ||
(pseudo.alloc_count == 2 && strcmp(imsi_pseudo, pseudo.previous) == 0)) {
/* Pseudonymous IMSI is allocated for given subscriber */
if (db_dealloc_imsi_pseudo(g_hlr->dbc, imsi_pseudo) != 0) {
vty_out(vty, "Failed to deallocate pseudonymous IMSI%s", VTY_NEWLINE);
return CMD_WARNING;
}
} else {
vty_out(vty, "%% Error: pseudonymous IMSI '%s' is not allocated to given subscriber%s", imsi_pseudo,
VTY_NEWLINE);
return CMD_WARNING;
}
return CMD_SUCCESS;
}
void hlr_vty_subscriber_init(void)
{
@ -639,4 +733,6 @@ void hlr_vty_subscriber_init(void)
install_element(ENABLE_NODE, &subscriber_aud3g_cmd);
install_element(ENABLE_NODE, &subscriber_imei_cmd);
install_element(ENABLE_NODE, &subscriber_nam_cmd);
install_element(ENABLE_NODE, &subscriber_imsi_pseudo_alloc_cmd);
install_element(ENABLE_NODE, &subscriber_imsi_pseudo_dealloc_cmd);
}

View File

@ -25,6 +25,12 @@ const struct log_info_cat hlr_log_info_cat[] = {
.color = "\033[1;34m",
.enabled = 1, .loglevel = LOGL_NOTICE,
},
[DPSEUDO] = {
.name = "DPSEUDO",
.description = "IMSI Pseudonymization",
.color = "\033[1;36m",
.enabled = 1, .loglevel = LOGL_NOTICE,
},
[DMSLOOKUP] = {
.name = "DMSLOOKUP",
.description = "Mobile Subscriber Lookup",

View File

@ -45,6 +45,7 @@ if ENABLE_EXT_TESTS
python-tests:
# don't run vty and ctrl tests concurrently so that the ports don't conflict
$(MAKE) vty-test
$(MAKE) vty-test-imsi-pseudo
$(MAKE) ctrl-test
$(MAKE) db-upgrade-equivalence-test
else
@ -68,6 +69,16 @@ vty-test:
$(U) $(srcdir)/$(VTY_TEST)
-rm -f $(VTY_TEST_DB) $(VTY_TEST_DB)-*
# IMSI pseudon VTY tests: don't share the DB with other VTY tests, so we can have deterministic "random" pseudo IMSIs
vty-test-imsi-pseudo:
-rm -f $(VTY_TEST_DB)
sqlite3 $(VTY_TEST_DB) < $(top_srcdir)/sql/hlr.sql
osmo_verify_transcript_vty.py -v \
-n OsmoHLR -p 4258 \
-r "$(top_builddir)/src/osmo-hlr -c $(top_srcdir)/doc/examples/osmo-hlr.cfg -l $(VTY_TEST_DB)" \
$(U) $(srcdir)/imsi_pseudo/imsi_pseudo.vty
-rm -f $(VTY_TEST_DB) $(VTY_TEST_DB)-*
CTRL_TEST_DB = hlr_ctrl_test.db
# To update the CTRL script from current application behavior,

View File

@ -0,0 +1,42 @@
OsmoHLR> enable
OsmoHLR# subscriber imsi 111111111111111 create
...
OsmoHLR# subscriber id 1 update imsi-pseudo alloc
% New pseudonymous IMSI allocated: 111111111111111
OsmoHLR# subscriber imsi 222222222222222 create
...
OsmoHLR# subscriber id 1 update imsi-pseudo alloc
% New pseudonymous IMSI allocated: 222222222222222
OsmoHLR# subscriber id 1 update imsi-pseudo dealloc 111111111111111
OsmoHLR# subscriber id 2 update imsi-pseudo alloc
% New pseudonymous IMSI allocated: 111111111111111
OsmoHLR# subscriber imsi 333333333333333 create
...
OsmoHLR# subscriber id 1 update imsi-pseudo alloc
% New pseudonymous IMSI allocated: 333333333333333
OsmoHLR# subscriber id 1 show
ID: 1
IMSI: 111111111111111
Pseudonymous IMSI (current): 333333333333333, i: 3
Pseudonymous IMSI (previous): 222222222222222
MSISDN: none
OsmoHLR# subscriber id 2 show
ID: 2
IMSI: 222222222222222
Pseudonymous IMSI (current): 111111111111111, i: 1
MSISDN: none
OsmoHLR# subscriber id 3 show
ID: 3
IMSI: 333333333333333
MSISDN: none
OsmoHLR# subscriber id 3 update imsi-pseudo alloc
% Error: all IMSIs are already allocated as pseudonymous IMSI
OsmoHLR# subscriber id 1 update imsi-pseudo dealloc 123
% Error: pseudonymous IMSI '123' is not allocated to given subscriber
OsmoHLR# subscriber imsi 444444444444444 create
...
OsmoHLR# subscriber id 1 update imsi-pseudo alloc
% Error: subscriber already has two pseudonymous IMSI allocated

View File

@ -93,6 +93,7 @@ log stderr
logging level db notice
logging level auc notice
logging level ss info
logging level pseudo notice
logging level mslookup notice
logging level lu notice
logging level dgsm notice

View File

@ -13,6 +13,8 @@ OsmoHLR# list
subscriber (imsi|msisdn|id|imei) IDENT update aud3g milenage k K (op|opc) OP_C [ind-bitlen] [<0-28>]
subscriber (imsi|msisdn|id|imei) IDENT update imei (none|IMEI)
subscriber (imsi|msisdn|id|imei) IDENT update network-access-mode (none|cs|ps|cs+ps)
subscriber (imsi|msisdn|id|imei) IDENT update imsi-pseudo alloc
subscriber (imsi|msisdn|id|imei) IDENT update imsi-pseudo dealloc IMSI_PSEUDO
show mslookup services
OsmoHLR# subscriber?
@ -132,6 +134,7 @@ OsmoHLR# subscriber imsi 123456789023000 update ?
aud3g Set UMTS authentication data (3G, and 2G with UMTS AKA)
imei Set IMEI of the subscriber (normally populated from MSC, no need to set this manually)
network-access-mode Set Network Access Mode (NAM) of the subscriber
imsi-pseudo Allocate or deallocate pseudonymous IMSI of the subscriber
OsmoHLR# subscriber imsi 123456789023000 update msisdn ?
none Remove MSISDN (phone number)