sgsn: Add a subscriber based authentication phase

This implements the MAP way of subscriber validation when the MS
tries to perform an Attach Request:

  1. perform authentication (optionally invoke the sendAuthInfo
     procedure), starts the Auth & Ciph procedure
  2. perform update location
  3. insert subscriber data
  4. finish the update location
  5. Attach Accept / Attach Reject

The authentication triplets are used and eventually updated if all of
them have been used.

This is currently accessible via the VTY interface by the following
commands:

  - update-subscriber imsi IMSI update-auth-info
  - update-subscriber imsi IMSI update-location-result (ok|ERR-CAUSE)

Sponsored-by: On-Waves ehf
diff --git a/openbsc/include/openbsc/gprs_sgsn.h b/openbsc/include/openbsc/gprs_sgsn.h
index 411462b..61b84d8 100644
--- a/openbsc/include/openbsc/gprs_sgsn.h
+++ b/openbsc/include/openbsc/gprs_sgsn.h
@@ -296,16 +296,25 @@
 /*
  * GPRS subscriber data
  */
-#define GPRS_SUBSCRIBER_UPDATE_PENDING (1 << 16)
-#define GPRS_SUBSCRIBER_CANCELLED      (1 << 17)
+#define GPRS_SUBSCRIBER_UPDATE_AUTH_INFO_PENDING	(1 << 16)
+#define GPRS_SUBSCRIBER_UPDATE_LOCATION_PENDING		(1 << 17)
+#define GPRS_SUBSCRIBER_CANCELLED			(1 << 18)
+
+#define GPRS_SUBSCRIBER_UPDATE_PENDING_MASK ( \
+		GPRS_SUBSCRIBER_UPDATE_LOCATION_PENDING | \
+		GPRS_SUBSCRIBER_UPDATE_AUTH_INFO_PENDING  \
+)
 
 void gprs_subscr_init(struct sgsn_instance *sgi);
-int gprs_subscr_request_update(struct sgsn_mm_ctx *mmctx);
+int gprs_subscr_request_update_location(struct sgsn_mm_ctx *mmctx);
+int gprs_subscr_request_auth_info(struct sgsn_mm_ctx *mmctx);
 void gprs_subscr_delete(struct gsm_subscriber *subscr);
 struct gsm_subscriber *gprs_subscr_get_or_create(const char *imsi);
+struct gsm_subscriber *gprs_subscr_get_or_create_by_mmctx( struct sgsn_mm_ctx *mmctx);
 struct gsm_subscriber *gprs_subscr_get_by_imsi(const char *imsi);
 void gprs_subscr_put_and_cancel(struct gsm_subscriber *subscr);
 void gprs_subscr_update(struct gsm_subscriber *subscr);
+void gprs_subscr_update_auth_info(struct gsm_subscriber *subscr);
 
 /* Called on subscriber data updates */
 void sgsn_update_subscriber_data(struct sgsn_mm_ctx *mmctx,
diff --git a/openbsc/src/gprs/gprs_subscriber.c b/openbsc/src/gprs/gprs_subscriber.c
index 1675331..1f21029 100644
--- a/openbsc/src/gprs/gprs_subscriber.c
+++ b/openbsc/src/gprs/gprs_subscriber.c
@@ -92,9 +92,18 @@
 	gprs_subscr_delete(subscr);
 }
 
-int gprs_subscr_query(struct gsm_subscriber *subscr)
+int gprs_subscr_query_auth_info(struct gsm_subscriber *subscr)
 {
-	/* TODO: Implement remote query to MSC, ... */
+	/* TODO: Implement remote query to HLR, ... */
+
+	LOGMMCTXP(LOGL_INFO, subscr->sgsn_data->mm,
+		  "subscriber auth info is not available (remote query NYI)\n");
+	return -ENOTSUP;
+}
+
+int gprs_subscr_location_update(struct gsm_subscriber *subscr)
+{
+	/* TODO: Implement remote query to HLR, ... */
 
 	LOGMMCTXP(LOGL_INFO, subscr->sgsn_data->mm,
 		  "subscriber data is not available (remote query NYI)\n");
@@ -105,56 +114,80 @@
 {
 	LOGMMCTXP(LOGL_DEBUG, subscr->sgsn_data->mm, "Updating subscriber data\n");
 
-	subscr->flags &= ~GPRS_SUBSCRIBER_UPDATE_PENDING;
+	subscr->flags &= ~GPRS_SUBSCRIBER_UPDATE_LOCATION_PENDING;
 	subscr->flags &= ~GSM_SUBSCRIBER_FIRST_CONTACT;
 
 	sgsn_update_subscriber_data(subscr->sgsn_data->mm, subscr);
 }
 
-int gprs_subscr_request_update(struct sgsn_mm_ctx *mmctx)
+void gprs_subscr_update_auth_info(struct gsm_subscriber *subscr)
+{
+	LOGMMCTXP(LOGL_DEBUG, subscr->sgsn_data->mm,
+		  "Updating subscriber authentication info\n");
+
+	subscr->flags &= ~GPRS_SUBSCRIBER_UPDATE_AUTH_INFO_PENDING;
+	subscr->flags &= ~GSM_SUBSCRIBER_FIRST_CONTACT;
+
+	sgsn_update_subscriber_data(subscr->sgsn_data->mm, subscr);
+}
+
+struct gsm_subscriber *gprs_subscr_get_or_create_by_mmctx(struct sgsn_mm_ctx *mmctx)
 {
 	struct gsm_subscriber *subscr = NULL;
-	int need_update = 0;
-	int rc;
 
-	LOGMMCTXP(LOGL_DEBUG, mmctx, "Requesting subscriber data update\n");
+	if (mmctx->subscr)
+		return subscr_get(mmctx->subscr);
 
-	if (mmctx->subscr) {
-		subscr = subscr_get(mmctx->subscr);
-	} else if (mmctx->imsi[0]) {
+	if (mmctx->imsi[0])
 		subscr = gprs_subscr_get_by_imsi(mmctx->imsi);
-		need_update = 1;
-	}
 
 	if (!subscr) {
 		subscr = gprs_subscr_get_or_create(mmctx->imsi);
 		subscr->flags |= GSM_SUBSCRIBER_FIRST_CONTACT;
-		need_update = 1;
 	}
 
 	if (strcpy(subscr->equipment.imei, mmctx->imei) != 0) {
 		strncpy(subscr->equipment.imei, mmctx->imei, GSM_IMEI_LENGTH-1);
 		subscr->equipment.imei[GSM_IMEI_LENGTH-1] = 0;
-		need_update = 1;
 	}
 
-	if (subscr->lac != mmctx->ra.lac) {
+	if (subscr->lac != mmctx->ra.lac)
 		subscr->lac = mmctx->ra.lac;
-		need_update = 1;
-	}
 
-	if (need_update) {
-		subscr->flags |= GPRS_SUBSCRIBER_UPDATE_PENDING;
-		if (!mmctx->subscr) {
-			subscr->sgsn_data->mm = mmctx;
-			mmctx->subscr = subscr_get(subscr);
-		}
+	subscr->sgsn_data->mm = mmctx;
+	mmctx->subscr = subscr_get(subscr);
 
-		rc = gprs_subscr_query(subscr);
-		subscr_put(subscr);
-		return rc;
-	}
-	gprs_subscr_update(subscr);
+	return subscr;
+}
+
+int gprs_subscr_request_update_location(struct sgsn_mm_ctx *mmctx)
+{
+	struct gsm_subscriber *subscr = NULL;
+	int rc;
+
+	LOGMMCTXP(LOGL_DEBUG, mmctx, "Requesting subscriber data update\n");
+
+	subscr = gprs_subscr_get_or_create_by_mmctx(mmctx);
+
+	subscr->flags |= GPRS_SUBSCRIBER_UPDATE_LOCATION_PENDING;
+
+	rc = gprs_subscr_location_update(subscr);
 	subscr_put(subscr);
-	return 0;
+	return rc;
+}
+
+int gprs_subscr_request_auth_info(struct sgsn_mm_ctx *mmctx)
+{
+	struct gsm_subscriber *subscr = NULL;
+	int rc;
+
+	LOGMMCTXP(LOGL_DEBUG, mmctx, "Requesting subscriber authentication info\n");
+
+	subscr = gprs_subscr_get_or_create_by_mmctx(mmctx);
+
+	subscr->flags |= GPRS_SUBSCRIBER_UPDATE_AUTH_INFO_PENDING;
+
+	rc = gprs_subscr_query_auth_info(subscr);
+	subscr_put(subscr);
+	return rc;
 }
diff --git a/openbsc/src/gprs/sgsn_auth.c b/openbsc/src/gprs/sgsn_auth.c
index b065c06..3755a51 100644
--- a/openbsc/src/gprs/sgsn_auth.c
+++ b/openbsc/src/gprs/sgsn_auth.c
@@ -23,7 +23,7 @@
 #include <openbsc/gprs_sgsn.h>
 #include <openbsc/gprs_gmm.h>
 #include <openbsc/gsm_subscriber.h>
-
+#include <openbsc/gsm_04_08_gprs.h>
 #include <openbsc/debug.h>
 
 const struct value_string auth_state_names[] = {
@@ -107,11 +107,12 @@
 		if (!mmctx->subscr)
 			return mmctx->auth_state;
 
-		if (mmctx->subscr->flags & GPRS_SUBSCRIBER_UPDATE_PENDING)
+		if (mmctx->subscr->flags & GPRS_SUBSCRIBER_UPDATE_PENDING_MASK)
 			return mmctx->auth_state;
 
 		if (mmctx->subscr->sgsn_data->authenticate &&
-		    !mmctx->is_authenticated)
+		    (!mmctx->is_authenticated ||
+		     mmctx->subscr->sgsn_data->auth_triplets_updated))
 			return SGSN_AUTH_AUTHENTICATE;
 
 		if (mmctx->subscr->authorized)
@@ -141,20 +142,60 @@
 	return SGSN_AUTH_REJECTED;
 }
 
+/*
+ * This function is directly called by e.g. the GMM layer. It returns either
+ * after calling sgsn_auth_update directly or after triggering an asynchronous
+ * procedure which will call sgsn_auth_update later on.
+ */
 int sgsn_auth_request(struct sgsn_mm_ctx *mmctx)
 {
+	struct gsm_subscriber *subscr;
+	struct gsm_auth_tuple *at;
+	int need_update_location;
+	int rc;
+
 	LOGMMCTXP(LOGL_DEBUG, mmctx, "Requesting authorization\n");
 
-	if (sgsn->cfg.auth_policy == SGSN_AUTH_POLICY_REMOTE && !mmctx->subscr) {
-		if (gprs_subscr_request_update(mmctx) >= 0) {
+	if (sgsn->cfg.auth_policy != SGSN_AUTH_POLICY_REMOTE) {
+		sgsn_auth_update(mmctx);
+		return 0;
+	}
+
+	need_update_location =
+		mmctx->subscr == NULL ||
+		mmctx->pending_req == GSM48_MT_GMM_ATTACH_REQ;
+
+	/* This has the side effect of registering the subscr with the mmctx */
+	subscr = gprs_subscr_get_or_create_by_mmctx(mmctx);
+	subscr_put(subscr);
+
+	OSMO_ASSERT(mmctx->subscr != NULL);
+
+	if (mmctx->subscr->sgsn_data->authenticate && !mmctx->is_authenticated) {
+		/* Find next tuple */
+		at = sgsn_auth_get_tuple(mmctx, mmctx->auth_triplet.key_seq);
+
+		if (!at) {
+			/* No valid tuple found, request fresh ones */
+			mmctx->auth_triplet.key_seq = GSM_KEY_SEQ_INVAL;
 			LOGMMCTXP(LOGL_INFO, mmctx,
-				  "Missing information, requesting subscriber data\n");
-			return 0;
+				  "Requesting authentication tuples\n");
+			rc = gprs_subscr_request_auth_info(mmctx);
+			if (rc >= 0)
+				return 0;
+
+			return rc;
 		}
+
+		mmctx->auth_triplet = *at;
+	} else if (need_update_location) {
+		LOGMMCTXP(LOGL_INFO, mmctx,
+			  "Missing information, requesting subscriber data\n");
+		if (gprs_subscr_request_update_location(mmctx) >= 0)
+			return 0;
 	}
 
 	sgsn_auth_update(mmctx);
-
 	return 0;
 }
 
@@ -162,6 +203,7 @@
 {
 	enum sgsn_auth_state auth_state;
 	struct gsm_subscriber *subscr = mmctx->subscr;
+	struct gsm_auth_tuple *at;
 
 	auth_state = sgsn_auth_state(mmctx);
 
@@ -170,13 +212,27 @@
 		  get_value_string(sgsn_auth_state_names, auth_state));
 
 	if (auth_state == SGSN_AUTH_UNKNOWN && subscr &&
-	    !(subscr->flags & GPRS_SUBSCRIBER_UPDATE_PENDING)) {
-		/* Reject requests if gprs_subscr_request_update fails */
+	    !(subscr->flags & GPRS_SUBSCRIBER_UPDATE_PENDING_MASK)) {
+		/* Reject requests if gprs_subscr_request_update_location fails */
 		LOGMMCTXP(LOGL_ERROR, mmctx,
 			  "Missing information, authorization not possible\n");
 		auth_state = SGSN_AUTH_REJECTED;
 	}
 
+	if (auth_state == SGSN_AUTH_AUTHENTICATE &&
+	    mmctx->auth_triplet.key_seq == GSM_KEY_SEQ_INVAL) {
+		/* The current tuple is not valid, but we are possibly called
+		 * because new auth tuples have been received */
+		at = sgsn_auth_get_tuple(mmctx, mmctx->auth_triplet.key_seq);
+		if (!at) {
+			LOGMMCTXP(LOGL_ERROR, mmctx,
+				  "Missing auth tuples, authorization not possible\n");
+			auth_state = SGSN_AUTH_REJECTED;
+		} else {
+			mmctx->auth_triplet = *at;
+		}
+	}
+
 	if (mmctx->auth_state == auth_state)
 		return;
 
@@ -188,6 +244,9 @@
 
 	switch (auth_state) {
 	case SGSN_AUTH_AUTHENTICATE:
+		if (subscr)
+			subscr->sgsn_data->auth_triplets_updated = 0;
+
 		gsm0408_gprs_authenticate(mmctx);
 		break;
 	case SGSN_AUTH_ACCEPTED:
diff --git a/openbsc/src/gprs/sgsn_vty.c b/openbsc/src/gprs/sgsn_vty.c
index 99c5985..9b925a8 100644
--- a/openbsc/src/gprs/sgsn_vty.c
+++ b/openbsc/src/gprs/sgsn_vty.c
@@ -428,13 +428,15 @@
 	}
 
 	if (subscr->flags)
-		vty_out(vty, "    Flags: %s%s%s%s",
+		vty_out(vty, "    Flags: %s%s%s%s%s",
 			subscr->flags & GSM_SUBSCRIBER_FIRST_CONTACT ?
 			"FIRST_CONTACT " : "",
 			subscr->flags & GPRS_SUBSCRIBER_CANCELLED ?
 			"CANCELLED " : "",
-			subscr->flags & GPRS_SUBSCRIBER_UPDATE_PENDING ?
-			"UPDATE_PENDING " : "",
+			subscr->flags & GPRS_SUBSCRIBER_UPDATE_LOCATION_PENDING ?
+			"UPDATE_LOCATION_PENDING " : "",
+			subscr->flags & GPRS_SUBSCRIBER_UPDATE_AUTH_INFO_PENDING ?
+			"AUTH_INFO_PENDING " : "",
 			VTY_NEWLINE);
 
 	vty_out(vty, "    Use count: %u%s", subscr->use_count, VTY_NEWLINE);
@@ -587,6 +589,68 @@
 	return CMD_SUCCESS;
 }
 
+#define UL_ERR_STR "system-failure|data-missing|unexpected-data-value|" \
+		   "unknown-subscriber|roaming-not-allowed"
+
+#define UL_ERR_HELP \
+		"Force error code SystemFailure\n" \
+		"Force error code DataMissing\n" \
+		"Force error code UnexpectedDataValue\n" \
+		"Force error code UnknownSubscriber\n" \
+		"Force error code RoamingNotAllowed\n"
+
+DEFUN(update_subscr_update_location_result, update_subscr_update_location_result_cmd,
+	UPDATE_SUBSCR_STR "update-location-result (ok|" UL_ERR_STR ")",
+	UPDATE_SUBSCR_HELP
+	"Complete the update location procedure\n"
+	"The update location request succeeded\n"
+	UL_ERR_HELP)
+{
+	const char *imsi = argv[0];
+	const char *ret_code_str = argv[1];
+
+	struct gsm_subscriber *subscr;
+
+	subscr = gprs_subscr_get_by_imsi(imsi);
+	if (!subscr) {
+		vty_out(vty, "%% unable to get subscriber record for %s\n", imsi);
+		return CMD_WARNING;
+	}
+	if (strcmp(ret_code_str, "ok") == 0)
+		subscr->authorized = 1;
+	else
+		subscr->authorized = 0;
+
+	gprs_subscr_update(subscr);
+
+	subscr_put(subscr);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(update_subscr_update_auth_info, update_subscr_update_auth_info_cmd,
+	UPDATE_SUBSCR_STR "update-auth-info",
+	UPDATE_SUBSCR_HELP
+	"Complete the send authentication info procedure\n")
+{
+	const char *imsi = argv[0];
+
+	struct gsm_subscriber *subscr;
+
+	subscr = gprs_subscr_get_by_imsi(imsi);
+	if (!subscr) {
+		vty_out(vty, "%% unable to get subscriber record for %s\n", imsi);
+		return CMD_WARNING;
+	}
+
+	gprs_subscr_update_auth_info(subscr);
+
+	subscr_put(subscr);
+
+	return CMD_SUCCESS;
+}
+
+
 int sgsn_vty_init(void)
 {
 	install_element_ve(&show_sgsn_cmd);
@@ -600,6 +664,8 @@
 	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);
+	install_element(ENABLE_NODE, &update_subscr_update_location_result_cmd);
+	install_element(ENABLE_NODE, &update_subscr_update_auth_info_cmd);
 
 	install_element(CONFIG_NODE, &cfg_sgsn_cmd);
 	install_node(&sgsn_node, config_write_sgsn);
diff --git a/openbsc/tests/sgsn/Makefile.am b/openbsc/tests/sgsn/Makefile.am
index 970311d..50c37d6 100644
--- a/openbsc/tests/sgsn/Makefile.am
+++ b/openbsc/tests/sgsn/Makefile.am
@@ -8,7 +8,8 @@
 sgsn_test_SOURCES = sgsn_test.c
 sgsn_test_LDFLAGS = \
 	-Wl,--wrap=sgsn_update_subscriber_data \
-	-Wl,--wrap=gprs_subscr_request_update
+	-Wl,--wrap=gprs_subscr_request_update_location \
+	-Wl,--wrap=gprs_subscr_request_auth_info
 
 sgsn_test_LDADD = \
 	$(top_builddir)/src/gprs/gprs_llc_parse.o \
diff --git a/openbsc/tests/sgsn/sgsn_test.c b/openbsc/tests/sgsn/sgsn_test.c
index b4f6fdb..c7eec7b 100644
--- a/openbsc/tests/sgsn/sgsn_test.c
+++ b/openbsc/tests/sgsn/sgsn_test.c
@@ -67,13 +67,22 @@
 	(*update_subscriber_data_cb)(mmctx, subscr);
 }
 
-/* override, requires '-Wl,--wrap=gprs_subscr_request_update' */
-int __real_gprs_subscr_request_update(struct sgsn_mm_ctx *mmctx);
-int (*subscr_request_update_cb)(struct sgsn_mm_ctx *mmctx) =
-	&__real_gprs_subscr_request_update;
+/* override, requires '-Wl,--wrap=gprs_subscr_request_update_location' */
+int __real_gprs_subscr_request_update_location(struct sgsn_mm_ctx *mmctx);
+int (*subscr_request_update_location_cb)(struct sgsn_mm_ctx *mmctx) =
+	&__real_gprs_subscr_request_update_location;
 
-int __wrap_gprs_subscr_request_update(struct sgsn_mm_ctx *mmctx) {
-	return (*subscr_request_update_cb)(mmctx);
+int __wrap_gprs_subscr_request_update_location(struct sgsn_mm_ctx *mmctx) {
+	return (*subscr_request_update_location_cb)(mmctx);
+};
+
+/* override, requires '-Wl,--wrap=gprs_subscr_request_auth_info' */
+int __real_gprs_subscr_request_auth_info(struct sgsn_mm_ctx *mmctx);
+int (*subscr_request_auth_info_cb)(struct sgsn_mm_ctx *mmctx) =
+	&__real_gprs_subscr_request_auth_info;
+
+int __wrap_gprs_subscr_request_auth_info(struct sgsn_mm_ctx *mmctx) {
+	return (*subscr_request_auth_info_cb)(mmctx);
 };
 
 static int count(struct llist_head *head)
@@ -525,6 +534,12 @@
 		0x54
 	};
 
+	/* DTAP - Authentication and Ciphering Resp */
+	static const unsigned char auth_ciph_resp[] = {
+		0x08, 0x13, 0x00, 0x22, 0x51, 0xe5, 0x51, 0xe5, 0x23, 0x09,
+		0x9a, 0x78, 0x56, 0x34, 0x12, 0x90, 0x78, 0x56, 0x01
+	};
+
 	/* DTAP - Attach Complete */
 	static const unsigned char attach_compl[] = {
 		0x08, 0x03
@@ -584,6 +599,19 @@
 
 	OSMO_ASSERT(ctx->mm_state == GMM_COMMON_PROC_INIT);
 
+	if (ctx->auth_state == SGSN_AUTH_AUTHENTICATE) {
+		/* we expect an auth & ciph request */
+		OSMO_ASSERT(sgsn_tx_counter == 1);
+
+		/* inject the auth & ciph response */
+		send_0408_message(ctx->llme, foreign_tlli,
+				  auth_ciph_resp, ARRAY_SIZE(auth_ciph_resp));
+
+		/* check that the MM context has not been removed due to a
+		 * failed authorization */
+		OSMO_ASSERT(ctx == sgsn_mm_ctx_by_tlli(foreign_tlli, &raid));
+	}
+
 	/* we expect an attach accept/reject */
 	OSMO_ASSERT(sgsn_tx_counter == 1);
 
@@ -622,9 +650,9 @@
 	sgsn->cfg.auth_policy = saved_auth_policy;
 }
 
-int my_subscr_request_update(struct sgsn_mm_ctx *mmctx) {
+int my_subscr_request_update_location(struct sgsn_mm_ctx *mmctx) {
 	int rc;
-	rc = __real_gprs_subscr_request_update(mmctx);
+	rc = __real_gprs_subscr_request_update_location(mmctx);
 	if (rc == -ENOTSUP) {
 		OSMO_ASSERT(mmctx->subscr);
 		gprs_subscr_update(mmctx->subscr);
@@ -632,13 +660,19 @@
 	return rc;
 };
 
+int my_subscr_request_auth_info(struct sgsn_mm_ctx *mmctx) {
+	gprs_subscr_update(mmctx->subscr);
+	return 0;
+};
+
 static void test_gmm_attach_subscr(void)
 {
 	const enum sgsn_auth_policy saved_auth_policy = sgsn->cfg.auth_policy;
 	struct gsm_subscriber *subscr;
 
 	sgsn_inst.cfg.auth_policy = SGSN_AUTH_POLICY_REMOTE;
-	subscr_request_update_cb = my_subscr_request_update;
+	subscr_request_update_location_cb = my_subscr_request_update_location;
+	subscr_request_auth_info_cb = my_subscr_request_auth_info;
 
 	subscr = gprs_subscr_get_or_create("123456789012345");
 	subscr->authorized = 1;
@@ -652,37 +686,18 @@
 	gprs_subscr_delete(subscr);
 
 	sgsn->cfg.auth_policy = saved_auth_policy;
-	subscr_request_update_cb = __real_gprs_subscr_request_update;
+	subscr_request_update_location_cb = __real_gprs_subscr_request_update_location;
+	subscr_request_auth_info_cb = __real_gprs_subscr_request_auth_info;
 }
 
-int my_subscr_request_update_fake_auth(struct sgsn_mm_ctx *mmctx) {
-	int rc;
-	rc = __real_gprs_subscr_request_update(mmctx);
-	if (rc == -ENOTSUP) {
-		struct gsm_subscriber *subscr;
-		int old_sgsn_tx_counter = sgsn_tx_counter;
+int my_subscr_request_auth_info_fake_auth(struct sgsn_mm_ctx *mmctx)
+{
+	/* Fake an authentication */
+	OSMO_ASSERT(mmctx->subscr);
+	mmctx->is_authenticated = 1;
+	gprs_subscr_update_auth_info(mmctx->subscr);
 
-		OSMO_ASSERT(mmctx->subscr);
-		/* Prevent subscr from being deleted */
-		subscr = subscr_get(mmctx->subscr);
-
-		/* Start authentication procedure */
-		gprs_subscr_update(subscr);
-
-		/* This will cause a GPRS AUTH AND CIPHERING REQ (cksn broken) */
-		OSMO_ASSERT(old_sgsn_tx_counter == sgsn_tx_counter - 1);
-
-		/* Restore sgsn_tx_counter to keep test_gmm_attach happy */
-		sgsn_tx_counter = old_sgsn_tx_counter;
-
-		/* Fake an authentication */
-		OSMO_ASSERT(subscr->sgsn_data->mm);
-		subscr->sgsn_data->mm->is_authenticated = 1;
-		gprs_subscr_update(subscr);
-
-		subscr_put(subscr);
-	}
-	return rc;
+	return 0;
 };
 
 static void test_gmm_attach_subscr_fake_auth(void)
@@ -691,7 +706,8 @@
 	struct gsm_subscriber *subscr;
 
 	sgsn_inst.cfg.auth_policy = SGSN_AUTH_POLICY_REMOTE;
-	subscr_request_update_cb = my_subscr_request_update_fake_auth;
+	subscr_request_update_location_cb = my_subscr_request_update_location;
+	subscr_request_auth_info_cb = my_subscr_request_auth_info_fake_auth;
 
 	subscr = gprs_subscr_get_or_create("123456789012345");
 	subscr->authorized = 1;
@@ -706,7 +722,50 @@
 	gprs_subscr_delete(subscr);
 
 	sgsn->cfg.auth_policy = saved_auth_policy;
-	subscr_request_update_cb = __real_gprs_subscr_request_update;
+	subscr_request_update_location_cb = __real_gprs_subscr_request_update_location;
+	subscr_request_auth_info_cb = __real_gprs_subscr_request_auth_info;
+}
+
+int my_subscr_request_auth_info_real_auth(struct sgsn_mm_ctx *mmctx)
+{
+	struct gsm_auth_tuple at = {
+		.sres = {0x51, 0xe5, 0x51, 0xe5},
+		.key_seq = 0
+	};
+
+	/* Fake an authentication */
+	OSMO_ASSERT(mmctx->subscr);
+	mmctx->subscr->sgsn_data->auth_triplets[0] = at;
+
+	gprs_subscr_update_auth_info(mmctx->subscr);
+
+	return 0;
+};
+
+static void test_gmm_attach_subscr_real_auth(void)
+{
+	const enum sgsn_auth_policy saved_auth_policy = sgsn->cfg.auth_policy;
+	struct gsm_subscriber *subscr;
+
+	sgsn_inst.cfg.auth_policy = SGSN_AUTH_POLICY_REMOTE;
+	subscr_request_update_location_cb = my_subscr_request_update_location;
+	subscr_request_auth_info_cb = my_subscr_request_auth_info_real_auth;
+
+	subscr = gprs_subscr_get_or_create("123456789012345");
+	subscr->authorized = 1;
+	subscr->sgsn_data->authenticate = 1;
+	subscr_put(subscr);
+
+	printf("Auth policy 'remote', triplet based auth: ");
+	test_gmm_attach();
+
+	subscr = gprs_subscr_get_by_imsi("123456789012345");
+	OSMO_ASSERT(subscr != NULL);
+	gprs_subscr_delete(subscr);
+
+	sgsn->cfg.auth_policy = saved_auth_policy;
+	subscr_request_update_location_cb = __real_gprs_subscr_request_update_location;
+	subscr_request_auth_info_cb = __real_gprs_subscr_request_auth_info;
 }
 
 /*
@@ -1232,6 +1291,7 @@
 	test_gmm_attach_acl();
 	test_gmm_attach_subscr();
 	test_gmm_attach_subscr_fake_auth();
+	test_gmm_attach_subscr_real_auth();
 	test_gmm_reject();
 	test_gmm_cancel();
 	test_gmm_ptmsi_allocation();
diff --git a/openbsc/tests/sgsn/sgsn_test.ok b/openbsc/tests/sgsn/sgsn_test.ok
index eff47c4..e54991c 100644
--- a/openbsc/tests/sgsn/sgsn_test.ok
+++ b/openbsc/tests/sgsn/sgsn_test.ok
@@ -9,6 +9,7 @@
 Auth policy 'closed': Testing GMM attach
 Auth policy 'remote': Testing GMM attach
 Auth policy 'remote', auth faked: Testing GMM attach
+Auth policy 'remote', triplet based auth: Testing GMM attach
 Testing GMM reject
   - Attach Request (invalid MI length)
   - Attach Request (invalid MI type)