[PATCH] openbsc[master]: SGSN: Integrate support for UMTS AKA

Neels Hofmeyr gerrit-no-reply at lists.osmocom.org
Fri Feb 24 05:47:24 UTC 2017


Hello Max, Harald Welte, Jenkins Builder, Holger Freyther,

I'd like you to reexamine a change.  Please visit

    https://gerrit.osmocom.org/1683

to look at the new patch set (#6).

SGSN: Integrate support for UMTS AKA

The general infrastructure for UMTS AKA is already in place:
* GSUP with capability to send us auth_vectors that contain
  either triplets or quintuples
* mm_context that holds such auth_vectors

Add:
* capability to send UMTS AUTN in GMM AUTH REQ
* parse extended UMTS RES
* on auth response, validate expected AKA with vector and received res/sres
* add Auth Failure message to receive resync AUTS token and
  * send to HLR
  * clear out-of-sync auth tuple
  * enter new state for when we're waiting for HLR to resync and send new
    tuples so that the next Auth Request will be handled

Original first half of this patch by: Harald Welte <laforge at gnumonks.org>

Full UMTS AKA procedure including AUTS resync tested to work against OsmoHLR
with R99 USIM and Milenage algorithm.

The sgsn_test.c needs adjustment because we're checking the vector's auth_types
now.

Depends: libosmocore change-ids
         I277fb3d407396dffa5c07a9c5454d87a415d393f
         If943731a78089f0aac3d55245de80596d01314a4
Related: OS#1956
Change-Id: Ie6a0cefba5e4e7f02cc2eaf6ec006ac07d5c1816
---
M openbsc/include/openbsc/gprs_sgsn.h
M openbsc/src/gprs/gprs_gmm.c
M openbsc/src/gprs/gprs_subscriber.c
M openbsc/src/gprs/sgsn_auth.c
M openbsc/tests/sgsn/sgsn_test.c
5 files changed, 205 insertions(+), 30 deletions(-)


  git pull ssh://gerrit.osmocom.org:29418/openbsc refs/changes/83/1683/6

diff --git a/openbsc/include/openbsc/gprs_sgsn.h b/openbsc/include/openbsc/gprs_sgsn.h
index c3cac7f..fd86174 100644
--- a/openbsc/include/openbsc/gprs_sgsn.h
+++ b/openbsc/include/openbsc/gprs_sgsn.h
@@ -73,6 +73,7 @@
 enum sgsn_auth_state {
 	SGSN_AUTH_UNKNOWN,
 	SGSN_AUTH_AUTHENTICATE,
+	SGSN_AUTH_UMTS_RESYNC,
 	SGSN_AUTH_ACCEPTED,
 	SGSN_AUTH_REJECTED
 };
@@ -446,7 +447,11 @@
 
 int gprs_subscr_init(struct sgsn_instance *sgi);
 int gprs_subscr_request_update_location(struct sgsn_mm_ctx *mmctx);
-int gprs_subscr_request_auth_info(struct sgsn_mm_ctx *mmctx);
+int gprs_subscr_request_auth_info(struct sgsn_mm_ctx *mmctx,
+				  const uint8_t *auts,
+				  const uint8_t *auts_rand);
+int gprs_subscr_auth_sync(struct gprs_subscr *subscr,
+			  const uint8_t *auts, const uint8_t *auts_rand);
 void gprs_subscr_cleanup(struct gprs_subscr *subscr);
 struct gprs_subscr *gprs_subscr_get_or_create(const char *imsi);
 struct gprs_subscr *gprs_subscr_get_or_create_by_mmctx( struct sgsn_mm_ctx *mmctx);
diff --git a/openbsc/src/gprs/gprs_gmm.c b/openbsc/src/gprs/gprs_gmm.c
index 9efe402..3f59a2b 100644
--- a/openbsc/src/gprs/gprs_gmm.c
+++ b/openbsc/src/gprs/gprs_gmm.c
@@ -83,6 +83,8 @@
 		[GSM48_IE_GMM_PTMSI_SIG]	= { TLV_TYPE_FIXED, 3 },
 		[GSM48_IE_GMM_AUTH_RAND]	= { TLV_TYPE_FIXED, 16 },
 		[GSM48_IE_GMM_AUTH_SRES]	= { TLV_TYPE_FIXED, 4 },
+		[GSM48_IE_GMM_AUTH_RES_EXT]	= { TLV_TYPE_TLV, 0 },
+		[GSM48_IE_GMM_AUTH_FAIL_PAR]	= { TLV_TYPE_TLV, 0 },
 		[GSM48_IE_GMM_IMEISV]		= { TLV_TYPE_TLV, 0 },
 		[GSM48_IE_GMM_DRX_PARAM]	= { TLV_TYPE_FIXED, 2 },
 		[GSM48_IE_GMM_MS_NET_CAPA]	= { TLV_TYPE_TLV, 0 },
@@ -554,8 +556,19 @@
 	return gsm48_gmm_sendmsg(msg, 1, mm, false);
 }
 
+/* determine if the MS/UE supports R99 or later */
+static bool mmctx_is_r99(const struct sgsn_mm_ctx *mm)
+{
+	if (mm->ms_network_capa.len < 1)
+		return false;
+	if (mm->ms_network_capa.buf[0] & 0x01)
+		return true;
+	return false;
+}
+
 /* 3GPP TS 24.008 Section 9.4.9: Authentication and Ciphering Request */
-static int gsm48_tx_gmm_auth_ciph_req(struct sgsn_mm_ctx *mm, uint8_t *rnd,
+static int gsm48_tx_gmm_auth_ciph_req(struct sgsn_mm_ctx *mm,
+				      const struct osmo_auth_vector *vec,
 				      uint8_t key_seq, bool force_standby)
 {
 	struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 AUTH CIPH REQ");
@@ -563,8 +576,14 @@
 	struct gsm48_auth_ciph_req *acreq;
 	uint8_t *m_rand, *m_cksn, rbyte;
 
-	LOGMMCTXP(LOGL_INFO, mm, "<- GPRS AUTH AND CIPHERING REQ (rand = %s)\n",
-		osmo_hexdump(rnd, 16));
+	LOGMMCTXP(LOGL_INFO, mm, "<- GPRS AUTH AND CIPHERING REQ (rand = %s",
+		  osmo_hexdump(vec->rand, sizeof(vec->rand)));
+	if (mmctx_is_r99(mm) && vec
+	    && (vec->auth_types & OSMO_AUTH_TYPE_UMTS)) {
+		LOGPC(DMM, LOGL_INFO, ", autn = %s)\n",
+		      osmo_hexdump(vec->autn, sizeof(vec->autn)));
+	} else
+		LOGPC(DMM, LOGL_INFO, ")\n");
 
 	mmctx2msgid(msg, mm);
 
@@ -588,16 +607,25 @@
 	mm->ac_ref_nr_used = acreq->ac_ref_nr;
 
 	/* Only if authentication is requested we need to set RAND + CKSN */
-	if (rnd) {
-		m_rand = msgb_put(msg, 16+1);
+	if (vec) {
+		m_rand = msgb_put(msg, sizeof(vec->rand) + 1);
 		m_rand[0] = GSM48_IE_GMM_AUTH_RAND;
-		memcpy(m_rand + 1, rnd, 16);
+		memcpy(m_rand + 1, vec->rand, sizeof(vec->rand));
+
 		/* § 10.5.1.2: */
 		m_cksn = msgb_put(msg, 1);
 		m_cksn[0] = (GSM48_IE_GMM_CIPH_CKSN << 4) | (key_seq & 0x07);
+
+		/* A Release99 or higher MS/UE must be able to handle
+		 * the optional AUTN IE.  If a classic GSM SIM is
+		 * inserted, it will simply ignore AUTN and just use
+		 * RAND */
+		if (mmctx_is_r99(mm) &&
+		    (vec->auth_types & OSMO_AUTH_TYPE_UMTS)) {
+			msgb_tlv_put(msg, GSM48_IE_GMM_AUTN,
+				     sizeof(vec->autn), vec->autn);
+		}
 	}
-	/* FIXME: add AUTN for 3g auth according to 3GPP TS 24.008 § 10.5.3.1.1 */
-	/* FIXME: make sure we don't send any other messages to the MS */
 
 	return gsm48_gmm_sendmsg(msg, 1, mm, false);
 }
@@ -619,6 +647,62 @@
 	return gsm48_gmm_sendmsg(msg, 0, mm, false);
 }
 
+/* check if the received authentication response matches */
+static bool check_auth_resp(struct sgsn_mm_ctx *ctx,
+			    bool is_utran,
+			    const struct osmo_auth_vector *vec,
+			    const uint8_t *res, uint8_t res_len)
+{
+	const uint8_t *expect_res;
+	uint8_t expect_res_len;
+	enum osmo_sub_auth_type expect_type;
+	const char *expect_str;
+
+	if (!vec)
+		return true; /* really!? */
+
+	/* On UTRAN (3G) we always expect UMTS AKA. On GERAN (2G) we sent AUTN
+	 * and expect UMTS AKA if there is R99 capability and our vector
+	 * supports UMTS AKA, otherwise we expect GSM AKA. */
+	if (is_utran
+	    || (mmctx_is_r99(ctx) && (vec->auth_types & OSMO_AUTH_TYPE_UMTS))) {
+		expect_type = OSMO_AUTH_TYPE_UMTS;
+		expect_str = "UMTS RES";
+		expect_res = vec->res;
+		expect_res_len = vec->res_len;
+	} else {
+		expect_type = OSMO_AUTH_TYPE_GSM;
+		expect_str = "GSM SRES";
+		expect_res = vec->sres;
+		expect_res_len = sizeof(vec->sres);
+	}
+	
+	if (!(vec->auth_types & expect_type)) {
+		LOGMMCTXP(LOGL_ERROR, ctx, "Auth error: auth vector does"
+			  " not provide the expected auth type:"
+			  " expected %s = 0x%x, auth_types are 0x%x\n",
+			  expect_str, expect_type, vec->auth_types);
+		return false;
+	}
+
+	if (!res)
+		goto auth_mismatch;
+
+	if (res_len != expect_res_len)
+		goto auth_mismatch;
+
+	if (memcmp(res, expect_res, res_len) != 0)
+		goto auth_mismatch;
+
+	/* Authorized! */
+	return true;
+
+auth_mismatch:
+	LOGMMCTXP(LOGL_ERROR, ctx, "Auth mismatch: expected %s = %s\n",
+		  expect_str, osmo_hexdump_nospc(expect_res, expect_res_len));
+	return false;
+}
+
 /* Section 9.4.10: Authentication and Ciphering Response */
 static int gsm48_rx_gmm_auth_ciph_resp(struct sgsn_mm_ctx *ctx,
 					struct msgb *msg)
@@ -627,6 +711,9 @@
 	struct gsm48_auth_ciph_resp *acr = (struct gsm48_auth_ciph_resp *)gh->data;
 	struct tlv_parsed tp;
 	struct gsm_auth_tuple *at;
+	const char *res_name = "(no response)";
+	uint8_t res[16];
+	uint8_t res_len;
 	int rc;
 
 	LOGMMCTXP(LOGL_INFO, ctx, "-> GPRS AUTH AND CIPH RESPONSE\n");
@@ -651,26 +738,34 @@
 			(msg->data + msg->len) - acr->data, 0, 0);
 
 	if (!TLVP_PRESENT(&tp, GSM48_IE_GMM_AUTH_SRES) ||
-	    !TLVP_PRESENT(&tp, GSM48_IE_GMM_IMEISV)) {
+	    !TLVP_PRESENT(&tp, GSM48_IE_GMM_IMEISV) ||
+	    TLVP_LEN(&tp,GSM48_IE_GMM_AUTH_SRES) != 4) {
 		/* TODO: missing mandatory IE, return STATUS or REJ? */
 		LOGMMCTXP(LOGL_ERROR, ctx, "Missing mandantory IE\n");
 		return -EINVAL;
 	}
 
-	/* Compare SRES with what we expected */
-	LOGMMCTXP(LOGL_DEBUG, ctx, "checking received auth info, SRES = %s\n",
-		  osmo_hexdump(TLVP_VAL(&tp, GSM48_IE_GMM_AUTH_SRES),
-			       TLVP_LEN(&tp, GSM48_IE_GMM_AUTH_SRES)));
+	/* Start with the good old 4-byte SRES */
+	memcpy(res, TLVP_VAL(&tp, GSM48_IE_GMM_AUTH_SRES), 4);
+	res_len = 4;
+	res_name = "GSM SRES";
+
+	/* Append extended RES as part of UMTS AKA, if any */
+	if (TLVP_PRESENT(&tp, GSM48_IE_GMM_AUTH_RES_EXT)) {
+		unsigned int l = TLVP_LEN(&tp, GSM48_IE_GMM_AUTH_RES_EXT);
+		if (l > sizeof(res)-4)
+			l = sizeof(res)-4;
+		memcpy(res+4, TLVP_VAL(&tp, GSM48_IE_GMM_AUTH_RES_EXT), l);
+		res_len += l;
+		res_name = "UMTS RES";
+	}
 
 	at = &ctx->auth_triplet;
 
-	if (TLVP_LEN(&tp, GSM48_IE_GMM_AUTH_SRES) != sizeof(at->vec.sres) ||
-	    memcmp(TLVP_VAL(&tp, GSM48_IE_GMM_AUTH_SRES), at->vec.sres,
-		   sizeof(at->vec.sres)) != 0) {
-
-		LOGMMCTXP(LOGL_NOTICE, ctx, "Received SRES doesn't match "
-			  "expected RES %s\n", osmo_hexdump(at->vec.sres,
-							    sizeof(at->vec.sres)));
+	LOGMMCTXP(LOGL_DEBUG, ctx, "checking auth: received %s = %s\n",
+		  res_name, osmo_hexdump(res, res_len));
+	rc = check_auth_resp(ctx, false, &at->vec, res, res_len);
+	if (!rc) {
 		rc = gsm48_tx_gmm_auth_ciph_rej(ctx);
 		mm_ctx_cleanup_free(ctx, "GPRS AUTH AND CIPH REJECT");
 		return rc;
@@ -685,6 +780,60 @@
 
 	/* Check if we can let the mobile station enter */
 	return gsm48_gmm_authorize(ctx);
+}
+
+/* Section 9.4.10: Authentication and Ciphering Failure */
+static int gsm48_rx_gmm_auth_ciph_fail(struct sgsn_mm_ctx *ctx,
+					struct msgb *msg)
+{
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(msg);
+	struct tlv_parsed tp;
+	const uint8_t gmm_cause = gh->data[0];
+	const uint8_t *auts;
+	int rc;
+
+	LOGMMCTXP(LOGL_INFO, ctx, "-> GPRS AUTH AND CIPH FAILURE (cause = %s)\n",
+		  get_value_string(gsm48_gmm_cause_names, gmm_cause));
+
+	tlv_parse(&tp, &gsm48_gmm_att_tlvdef, gh->data+1, msg->len - 1, 0, 0);
+
+	/* Only if GMM cause is present and the AUTS is provided, we can
+	 * start re-sync procedure */
+	if (gmm_cause == GMM_CAUSE_SYNC_FAIL &&
+	    TLVP_PRESENT(&tp, GSM48_IE_GMM_AUTH_FAIL_PAR)) {
+		if (TLVP_LEN(&tp, GSM48_IE_GMM_AUTH_FAIL_PAR) != 14) {
+			LOGMMCTXP(LOGL_ERROR, ctx, "AUTS IE has wrong size:"
+				  " expected %d, got %u\n", 14,
+				  TLVP_LEN(&tp, GSM48_IE_GMM_AUTH_FAIL_PAR));
+			return -EINVAL;
+		}
+		auts = TLVP_VAL(&tp, GSM48_IE_GMM_AUTH_FAIL_PAR);
+
+		LOGMMCTXP(LOGL_INFO, ctx,
+			  "R99 AUTHENTICATION SYNCH (AUTS = %s)\n",
+			  osmo_hexdump_nospc(auts, 14));
+
+		/* make sure we'll refresh the auth_triplet in
+		 * sgsn_auth_update() */
+		ctx->auth_triplet.key_seq = GSM_KEY_SEQ_INVAL;
+
+		/* make sure we'll retry authentication after the resync */
+		ctx->auth_state = SGSN_AUTH_UMTS_RESYNC;
+
+		/* Send AUTS to HLR and wait for new Auth Info Result */
+		rc = gprs_subscr_request_auth_info(ctx, auts,
+						   ctx->auth_triplet.vec.rand);
+		if (!rc)
+			return 0;
+		/* on error, fall through to send a reject */
+		LOGMMCTXP(LOGL_ERROR, ctx,
+			  "Sending AUTS to HLR failed (rc = %d)\n", rc);
+	}
+
+	LOGMMCTXP(LOGL_NOTICE, ctx, "Authentication failed\n");
+	rc = gsm48_tx_gmm_auth_ciph_rej(ctx);
+	mm_ctx_cleanup_free(ctx, "GPRS AUTH FAILURE");
+	return rc;
 }
 
 static void extract_subscr_msisdn(struct sgsn_mm_ctx *ctx)
@@ -865,8 +1014,8 @@
 		struct gsm_auth_tuple *at = &ctx->auth_triplet;
 
 		mmctx_timer_start(ctx, 3360, sgsn->cfg.timers.T3360);
-		return gsm48_tx_gmm_auth_ciph_req(ctx, at->vec.rand,
-						  at->key_seq, false);
+		return gsm48_tx_gmm_auth_ciph_req(ctx, &at->vec, at->key_seq,
+						  false);
 	}
 
 	if (ctx->auth_state == SGSN_AUTH_AUTHENTICATE && ctx->is_authenticated &&
@@ -1911,6 +2060,9 @@
 			goto null_mmctx;
 		rc = gsm48_rx_gmm_auth_ciph_resp(mmctx, msg);
 		break;
+	case GSM48_MT_GMM_AUTH_CIPH_FAIL:
+		rc = gsm48_rx_gmm_auth_ciph_fail(mmctx, msg);
+		break;
 	default:
 		LOGMMCTXP(LOGL_NOTICE, mmctx, "Unknown GSM 04.08 GMM msg type 0x%02x\n",
 			gh->msg_type);
@@ -1979,7 +2131,7 @@
 		}
 		at = &mm->auth_triplet;
 
-		gsm48_tx_gmm_auth_ciph_req(mm, at->vec.rand, at->key_seq, false);
+		gsm48_tx_gmm_auth_ciph_req(mm, &at->vec, at->key_seq, false);
 		osmo_timer_schedule(&mm->timer, sgsn->cfg.timers.T3360, 0);
 		break;
 	case 3370:	/* waiting for IDENTITY RESPONSE */
diff --git a/openbsc/src/gprs/gprs_subscriber.c b/openbsc/src/gprs/gprs_subscriber.c
index 5f426f8..2042ec6 100644
--- a/openbsc/src/gprs/gprs_subscriber.c
+++ b/openbsc/src/gprs/gprs_subscriber.c
@@ -766,14 +766,21 @@
 	return gprs_subscr_tx_gsup_message(subscr, &gsup_msg);
 }
 
-int gprs_subscr_query_auth_info(struct gprs_subscr *subscr)
+static int gprs_subscr_query_auth_info(struct gprs_subscr *subscr,
+				       const uint8_t *auts,
+				       const uint8_t *auts_rand)
 {
 	struct osmo_gsup_message gsup_msg = {0};
 
-	LOGGSUBSCRP(LOGL_INFO, subscr,
-		"subscriber auth info is not available\n");
+	/* Make sure we have a complete resync or clearly no resync. */
+	OSMO_ASSERT((auts != NULL) == (auts_rand != NULL));
+
+	LOGGSUBSCRP(LOGL_INFO, subscr, "requesting auth info%s\n",
+		    auts ? " with AUTS (UMTS Resynch)" : "");
 
 	gsup_msg.message_type = OSMO_GSUP_MSGT_SEND_AUTH_INFO_REQUEST;
+	gsup_msg.auts = auts;
+	gsup_msg.rand = auts_rand;
 	return gprs_subscr_tx_gsup_message(subscr, &gsup_msg);
 }
 
@@ -854,7 +861,16 @@
 	return rc;
 }
 
-int gprs_subscr_request_auth_info(struct sgsn_mm_ctx *mmctx)
+/*! \brief Send Update Auth Info request via GSUP, with or without resync.
+ *  \param[in] mmctx  MM context to request authentication tuples for.
+ *  \param[in] auts  14 octet AUTS token for UMTS resync, or NULL.
+ *  \param[in] auts_rand  16 octet Random token for UMTS resync, or NULL.
+ * In case of normal Authentication Info request, both \a auts and \a auts_rand
+ * must be NULL. For resync, both must be non-NULL.
+ */
+int gprs_subscr_request_auth_info(struct sgsn_mm_ctx *mmctx,
+				  const uint8_t *auts,
+				  const uint8_t *auts_rand)
 {
 	struct gprs_subscr *subscr = NULL;
 	int rc;
@@ -865,7 +881,7 @@
 
 	subscr->flags |= GPRS_SUBSCRIBER_UPDATE_AUTH_INFO_PENDING;
 
-	rc = gprs_subscr_query_auth_info(subscr);
+	rc = gprs_subscr_query_auth_info(subscr, auts, auts_rand);
 	gprs_subscr_put(subscr);
 	return rc;
 }
diff --git a/openbsc/src/gprs/sgsn_auth.c b/openbsc/src/gprs/sgsn_auth.c
index df7ee37..a64339c 100644
--- a/openbsc/src/gprs/sgsn_auth.c
+++ b/openbsc/src/gprs/sgsn_auth.c
@@ -32,6 +32,7 @@
 	{ SGSN_AUTH_REJECTED,	"rejected"},
 	{ SGSN_AUTH_UNKNOWN,	"unknown"},
 	{ SGSN_AUTH_AUTHENTICATE, "authenticate" },
+	{ SGSN_AUTH_UMTS_RESYNC, "UMTS-resync" },
 	{ 0, NULL }
 };
 
@@ -182,7 +183,7 @@
 			mmctx->auth_triplet.key_seq = GSM_KEY_SEQ_INVAL;
 			LOGMMCTXP(LOGL_INFO, mmctx,
 				  "Requesting authentication tuples\n");
-			rc = gprs_subscr_request_auth_info(mmctx);
+			rc = gprs_subscr_request_auth_info(mmctx, NULL, NULL);
 			if (rc >= 0)
 				return 0;
 
diff --git a/openbsc/tests/sgsn/sgsn_test.c b/openbsc/tests/sgsn/sgsn_test.c
index 6eabdde..2f1513a 100644
--- a/openbsc/tests/sgsn/sgsn_test.c
+++ b/openbsc/tests/sgsn/sgsn_test.c
@@ -1164,6 +1164,7 @@
 {
 	struct gsm_auth_tuple at = {
 		.vec.sres = {0x51, 0xe5, 0x51, 0xe5},
+		.vec.auth_types = OSMO_AUTH_TYPE_GSM,
 		.key_seq = 0
 	};
 

-- 
To view, visit https://gerrit.osmocom.org/1683
To unsubscribe, visit https://gerrit.osmocom.org/settings

Gerrit-MessageType: newpatchset
Gerrit-Change-Id: Ie6a0cefba5e4e7f02cc2eaf6ec006ac07d5c1816
Gerrit-PatchSet: 6
Gerrit-Project: openbsc
Gerrit-Branch: master
Gerrit-Owner: Neels Hofmeyr <nhofmeyr at sysmocom.de>
Gerrit-Reviewer: Harald Welte <laforge at gnumonks.org>
Gerrit-Reviewer: Holger Freyther <holger at freyther.de>
Gerrit-Reviewer: Jenkins Builder
Gerrit-Reviewer: Max <msuraev at sysmocom.de>
Gerrit-Reviewer: Neels Hofmeyr <nhofmeyr at sysmocom.de>


More information about the gerrit-log mailing list