sgsn: Add support for authentication triplets

This commit add data structures, functions, initialization, and VTY
commands for per subscriber authentication triplets.

The following VTY command is added:

  - update-subscriber imsi IMSI \
    insert auth-triplet <1-5> sres SRES rand RAND kc KC

Note that the triplets are not really used by the SGSN yet.

Sponsored-by: On-Waves ehf
diff --git a/openbsc/include/openbsc/gprs_sgsn.h b/openbsc/include/openbsc/gprs_sgsn.h
index d3cd8bb..411462b 100644
--- a/openbsc/include/openbsc/gprs_sgsn.h
+++ b/openbsc/include/openbsc/gprs_sgsn.h
@@ -273,6 +273,8 @@
 
 struct sgsn_subscriber_data {
 	struct sgsn_mm_ctx	*mm;
+	struct gsm_auth_tuple	auth_triplets[5];
+	int			auth_triplets_updated;
 	int			authenticate;
 };
 
@@ -288,6 +290,8 @@
 int sgsn_auth_request(struct sgsn_mm_ctx *mm);
 enum sgsn_auth_state sgsn_auth_state(struct sgsn_mm_ctx *mm);
 void sgsn_auth_update(struct sgsn_mm_ctx *mm);
+struct gsm_auth_tuple *sgsn_auth_get_tuple(struct sgsn_mm_ctx *mmctx,
+					   unsigned key_seq);
 
 /*
  * GPRS subscriber data
diff --git a/openbsc/src/gprs/gprs_subscriber.c b/openbsc/src/gprs/gprs_subscriber.c
index cf0af90..1675331 100644
--- a/openbsc/src/gprs/gprs_subscriber.c
+++ b/openbsc/src/gprs/gprs_subscriber.c
@@ -36,9 +36,13 @@
 static struct sgsn_subscriber_data *sgsn_subscriber_data_alloc(void *ctx)
 {
 	struct sgsn_subscriber_data *sdata;
+	int idx;
 
 	sdata = talloc_zero(ctx, struct sgsn_subscriber_data);
 
+	for (idx = 0; idx < ARRAY_SIZE(sdata->auth_triplets); idx++)
+	     sdata->auth_triplets[idx].key_seq = GSM_KEY_SEQ_INVAL;
+
 	return sdata;
 }
 
diff --git a/openbsc/src/gprs/sgsn_auth.c b/openbsc/src/gprs/sgsn_auth.c
index 5f90f5c..b065c06 100644
--- a/openbsc/src/gprs/sgsn_auth.c
+++ b/openbsc/src/gprs/sgsn_auth.c
@@ -203,3 +203,40 @@
 		break;
 	}
 }
+
+struct gsm_auth_tuple *sgsn_auth_get_tuple(struct sgsn_mm_ctx *mmctx,
+					   unsigned key_seq)
+{
+	unsigned count;
+	unsigned idx;
+	struct gsm_auth_tuple *at = NULL;
+
+	struct sgsn_subscriber_data *sdata;
+
+	if (!mmctx->subscr)
+		return NULL;
+
+	if (key_seq == GSM_KEY_SEQ_INVAL)
+		/* Start with 0 after increment module array size */
+		idx = ARRAY_SIZE(sdata->auth_triplets) - 1;
+	else
+		idx = key_seq;
+
+	sdata = mmctx->subscr->sgsn_data;
+
+	/* Find next tuple */
+	for (count = ARRAY_SIZE(sdata->auth_triplets); count > 0; count--) {
+		idx = (idx + 1) % ARRAY_SIZE(sdata->auth_triplets);
+
+		if (sdata->auth_triplets[idx].key_seq == GSM_KEY_SEQ_INVAL)
+			continue;
+
+		if (sdata->auth_triplets[idx].use_count == 0) {
+			at = &sdata->auth_triplets[idx];
+			at->use_count = 1;
+			return at;
+		}
+	}
+
+	return NULL;
+}
diff --git a/openbsc/src/gprs/sgsn_vty.c b/openbsc/src/gprs/sgsn_vty.c
index 4bb6a86..99c5985 100644
--- a/openbsc/src/gprs/sgsn_vty.c
+++ b/openbsc/src/gprs/sgsn_vty.c
@@ -380,6 +380,8 @@
 static void subscr_dump_full_vty(struct vty *vty, struct gsm_subscriber *subscr, int pending)
 {
 	char expire_time[200];
+	struct gsm_auth_tuple *at;
+	int at_idx;
 
 	vty_out(vty, "    ID: %llu, Authorized: %d%s", subscr->id,
 		subscr->authorized, VTY_NEWLINE);
@@ -398,6 +400,25 @@
 	if (strlen(subscr->equipment.imei) > 0)
 		vty_out(vty, "    IMEI: %s%s", subscr->equipment.imei, VTY_NEWLINE);
 
+	for (at_idx = 0; at_idx < ARRAY_SIZE(subscr->sgsn_data->auth_triplets);
+	     at_idx++) {
+		at = &subscr->sgsn_data->auth_triplets[at_idx];
+		if (at->key_seq == GSM_KEY_SEQ_INVAL)
+			continue;
+
+		vty_out(vty, "    A3A8 tuple (used %d times): ",
+			at->use_count);
+		vty_out(vty, "     seq # : %d, ",
+			at->key_seq);
+		vty_out(vty, "     RAND  : %s, ",
+			osmo_hexdump(at->rand, sizeof(at->rand)));
+		vty_out(vty, "     SRES  : %s, ",
+			osmo_hexdump(at->sres, sizeof(at->sres)));
+		vty_out(vty, "     Kc    : %s%s",
+			osmo_hexdump(at->kc, sizeof(at->kc)),
+			VTY_NEWLINE);
+	}
+
 	/* print the expiration time of a subscriber */
 	if (subscr->expire_lu) {
 		strftime(expire_time, sizeof(expire_time),
@@ -440,10 +461,12 @@
 	"Use the IMSI to select the subscriber\n" \
 	"The IMSI\n"
 
+#define UPDATE_SUBSCR_INSERT_HELP "Insert data into the subscriber record\n"
+
 DEFUN(update_subscr_insert, update_subscr_insert_cmd,
 	UPDATE_SUBSCR_STR "insert (authorized|authenticate) (0|1)",
 	UPDATE_SUBSCR_HELP
-	"Insert data into the subscriber record\n"
+	UPDATE_SUBSCR_INSERT_HELP
 	"Authorize the subscriber to attach\n"
 	"New option value\n")
 {
@@ -469,6 +492,59 @@
 	return CMD_SUCCESS;
 }
 
+DEFUN(update_subscr_insert_auth_triplet, update_subscr_insert_auth_triplet_cmd,
+	UPDATE_SUBSCR_STR "insert auth-triplet <1-5> sres SRES rand RAND kc KC",
+	UPDATE_SUBSCR_HELP
+	UPDATE_SUBSCR_INSERT_HELP
+	"Update authentication triplet\n"
+	"Triplet index\n"
+	"Set SRES value\nSRES value (4 byte) in hex\n"
+	"Set RAND value\nRAND value (16 byte) in hex\n"
+	"Set Kc value\nKc value (8 byte) in hex\n")
+{
+	const char *imsi = argv[0];
+	const int cksn = atoi(argv[1]) - 1;
+	const char *sres_str = argv[2];
+	const char *rand_str = argv[3];
+	const char *kc_str = argv[4];
+	struct gsm_auth_tuple at = {0,};
+
+	struct gsm_subscriber *subscr;
+
+	subscr = gprs_subscr_get_or_create(imsi);
+	if (!subscr) {
+		vty_out(vty, "%% unable get subscriber record for %s\n", imsi);
+		return CMD_WARNING;
+	}
+
+	OSMO_ASSERT(subscr->sgsn_data);
+
+	if (!osmo_hexparse(sres_str, &at.sres[0], sizeof(at.sres)) < 0) {
+		vty_out(vty, "%% invalid SRES value '%s'\n", sres_str);
+		goto failed;
+	}
+	if (!osmo_hexparse(rand_str, &at.rand[0], sizeof(at.rand)) < 0) {
+		vty_out(vty, "%% invalid RAND value '%s'\n", rand_str);
+		goto failed;
+	}
+	if (!osmo_hexparse(kc_str, &at.kc[0], sizeof(at.kc)) < 0) {
+		vty_out(vty, "%% invalid Kc value '%s'\n", kc_str);
+		goto failed;
+	}
+	at.key_seq = cksn;
+
+	subscr->sgsn_data->auth_triplets[cksn] = at;
+	subscr->sgsn_data->auth_triplets_updated = 1;
+
+	subscr_put(subscr);
+
+	return CMD_SUCCESS;
+
+failed:
+	subscr_put(subscr);
+	return CMD_SUCCESS;
+}
+
 DEFUN(update_subscr_cancel, update_subscr_cancel_cmd,
 	UPDATE_SUBSCR_STR "cancel",
 	UPDATE_SUBSCR_HELP
@@ -521,6 +597,7 @@
 	install_element_ve(&show_subscr_cache_cmd);
 
 	install_element(ENABLE_NODE, &update_subscr_insert_cmd);
+	install_element(ENABLE_NODE, &update_subscr_insert_auth_triplet_cmd);
 	install_element(ENABLE_NODE, &update_subscr_cancel_cmd);
 	install_element(ENABLE_NODE, &update_subscr_commit_cmd);
 
diff --git a/openbsc/tests/sgsn/sgsn_test.c b/openbsc/tests/sgsn/sgsn_test.c
index f5f6820..b4f6fdb 100644
--- a/openbsc/tests/sgsn/sgsn_test.c
+++ b/openbsc/tests/sgsn/sgsn_test.c
@@ -249,6 +249,70 @@
 	update_subscriber_data_cb = __real_sgsn_update_subscriber_data;
 }
 
+static void test_auth_triplets(void)
+{
+	struct gsm_subscriber *s1, *s1found;
+	const char *imsi1 = "1234567890";
+	struct gsm_auth_tuple *at;
+	struct sgsn_mm_ctx *ctx;
+	struct gprs_ra_id raid = { 0, };
+	uint32_t local_tlli = 0xffeeddcc;
+	struct gprs_llc_llme *llme;
+
+	printf("Testing authentication triplet handling\n");
+
+	/* Check for emptiness */
+	OSMO_ASSERT(gprs_subscr_get_by_imsi(imsi1) == NULL);
+
+	/* Allocate entry 1 */
+	s1 = gprs_subscr_get_or_create(imsi1);
+	s1->flags |= GSM_SUBSCRIBER_FIRST_CONTACT;
+	s1found = gprs_subscr_get_by_imsi(imsi1);
+	OSMO_ASSERT(s1found == s1);
+	subscr_put(s1found);
+
+	/* Create a context */
+	OSMO_ASSERT(count(gprs_llme_list()) == 0);
+	ctx = alloc_mm_ctx(local_tlli, &raid);
+
+	/* Attach s1 to ctx */
+	ctx->subscr = subscr_get(s1);
+	ctx->subscr->sgsn_data->mm = ctx;
+
+	/* Try to get auth tuple */
+	at = sgsn_auth_get_tuple(ctx, GSM_KEY_SEQ_INVAL);
+	OSMO_ASSERT(at == NULL);
+
+	/* Add triplets */
+	s1->sgsn_data->auth_triplets[0].key_seq = 0;
+	s1->sgsn_data->auth_triplets[1].key_seq = 1;
+	s1->sgsn_data->auth_triplets[2].key_seq = 2;
+
+	/* Try to get auth tuple */
+	at = sgsn_auth_get_tuple(ctx, GSM_KEY_SEQ_INVAL);
+	OSMO_ASSERT(at != NULL);
+	OSMO_ASSERT(at->key_seq == 0);
+	OSMO_ASSERT(at->use_count == 1);
+	at = sgsn_auth_get_tuple(ctx, at->key_seq);
+	OSMO_ASSERT(at != NULL);
+	OSMO_ASSERT(at->key_seq == 1);
+	OSMO_ASSERT(at->use_count == 1);
+	at = sgsn_auth_get_tuple(ctx, at->key_seq);
+	OSMO_ASSERT(at != NULL);
+	OSMO_ASSERT(at->key_seq == 2);
+	OSMO_ASSERT(at->use_count == 1);
+	at = sgsn_auth_get_tuple(ctx, at->key_seq);
+	OSMO_ASSERT(at == NULL);
+
+	/* Free MM context and subscriber */
+	subscr_put(s1);
+	llme = ctx->llme;
+	sgsn_mm_ctx_free(ctx);
+	s1found = gprs_subscr_get_by_imsi(imsi1);
+	OSMO_ASSERT(s1found == NULL);
+	gprs_llgmm_assign(llme, local_tlli, 0xffffffff, GPRS_ALGO_GEA0, NULL);
+}
+
 /*
  * Test that a GMM Detach will remove the MMCTX and the
  * associated LLME.
@@ -1159,6 +1223,7 @@
 
 	test_llme();
 	test_subscriber();
+	test_auth_triplets();
 	test_gmm_detach();
 	test_gmm_detach_power_off();
 	test_gmm_detach_no_mmctx();
diff --git a/openbsc/tests/sgsn/sgsn_test.ok b/openbsc/tests/sgsn/sgsn_test.ok
index 7d739ad..eff47c4 100644
--- a/openbsc/tests/sgsn/sgsn_test.ok
+++ b/openbsc/tests/sgsn/sgsn_test.ok
@@ -1,5 +1,6 @@
 Testing LLME allocations
 Testing core subscriber data API
+Testing authentication triplet handling
 Testing GMM detach
 Testing GMM detach (power off)
 Testing GMM detach (no MMCTX)