A5/n Ciph: request Classmark Update if missing

When the VLR requests a Ciphering Mode with vlr_ops.set_ciph_mode(), and if we
need a ciph algo flag from a Classmark information that is not yet known
(usually CM 2 during LU), send a BSSMAP Classmark Request to get it.

To manage the intermission of the Classmark Request, add
- msc_classmark_request_then_cipher_mode_cmd(),
- state SUBSCR_CONN_S_WAIT_CLASSMARK_UPDATE,
- event SUBSCR_CONN_E_CLASSMARK_UPDATE.

From state AUTH_CIPH, switch to state WAIT_CLASSMARK_UPDATE. Once the BSSMAP
Classmark Response, is received, switch back to SUBSCR_CONN_S_AUTH_CIPH and
re-initiate Ciphering Mode.

To be able to re-enter the Ciphering Mode algo decision, factor it out into
msc_geran_set_cipher_mode().

Rationale:

In the following commit, essentially we stopped supporting A5/3 ciphering:

commit 71330720b6efdda2fcfd3e9c0cb45f89e32e5670
"MSC: Intersect configured A5 algorithms with MS-supported ones"
Change-Id: Id124923ee52a357cb7d3e04d33f585214774f3a3

A5/3 was no longer supported because from that commit on, we strictly checked
the MS-supported ciphers, but we did not have Classmark 2 available during
Location Updating.

This patch changes that: when Classmark 2 is missing, actively request it by a
BSSMAP Classmark Request; continue Ciphering only after the Response. Always
request missing Classmark, even if a lesser cipher were configured available.

If the Classmark Update response fails to come in, cause an attach failure.
Instead, we could attempt to use a lesser cipher that is also enabled. That is
left as a future feature, should that become relevant. I think it's unlikely.

Technically, we could now end up requesting a Classmark Updating both during LU
(vlr_lu_fsm) and CM Service/Paging Response (proc_arq_fsm), but in practice the
only time we lack a Classmark is: during Location Updating with A5/3 enabled.
A5/1 support is indicated in CM1 which is always available, and A5/3 support is
indicated in CM2, which is always available during CM Service Request as well
as Paging Response. So this patch has practical relevance only for Location
Updating. For networks that permit only A5/3, this patch fixes Location
Updating. For networks that support A5/3 and A5/1, so far we always used A5/1
during LU, and after this patch we request CM2 and likely use A5/3 instead.

In msc_vlr_test_gsm_ciph, verify that requesting Classmark 2 for A5/3 works
during LU. Also verify that the lack of a Classmark Response results in attach
failure.

In msc_vlr_test_gsm_ciph, a hacky unit test fakes a situation where a CM2 is
missing during proc_arq_fsm and proves that that code path works, even though
the practical relevance is currently zero. It would only become interesting if
ciphering algorithms A5/4 and higher became relevant, because support of those
would be indicated in Classmark 3, which would always require a Classmark
Request.

Related: OS#3043
Depends: I4a2e1d3923e33912579c4180aa1ff8e8f5abb7e7 (libosmocore)
Change-Id: I73c7cb6a86624695bd9c0f59abb72e2fdc655131
diff --git a/src/libmsc/gsm_04_08.c b/src/libmsc/gsm_04_08.c
index b942a03..564e90b 100644
--- a/src/libmsc/gsm_04_08.c
+++ b/src/libmsc/gsm_04_08.c
@@ -119,13 +119,16 @@
 	return classmark2_is_r99(cm->classmark2, cm->classmark2_len);
 }
 
-/* Determine if the given CLASSMARK (1/2/3) value permits a given A5/n cipher */
-static bool classmark_supports_a5(const struct gsm_classmark *cm, uint8_t a5)
+/* Determine if the given CLASSMARK (1/2/3) value permits a given A5/n cipher.
+ * Return 1 when the given A5/n is permitted, 0 when not, and negative if the respective MS CLASSMARK is
+ * not known, where the negative number indicates the classmark type: -2 means Classmark 2 is not
+ * available. */
+static int classmark_supports_a5(const struct gsm_classmark *cm, uint8_t a5)
 {
 	switch (a5) {
 	case 0:
 		/* all phones must implement A5/0, see 3GPP TS 43.020 4.9 */
-		return true;
+		return 1;
 	case 1:
 		/* 3GPP TS 43.020 4.9 requires A5/1 to be suppored by all phones and actually states:
 		 * "The network shall not provide service to an MS which indicates that it does not
@@ -134,25 +137,24 @@
 		/* See 3GPP TS 24.008 10.5.1.7 */
 		if (!cm->classmark1_set) {
 			DEBUGP(DMSC, "CLASSMARK 1 unknown, assuming MS supports A5/1\n");
-			return true;
+			return -1;
 		} else {
 			if (cm->classmark1.a5_1)
-				return false;	/* Inverted logic for this bit! */
+				return 0;	/* Inverted logic for this bit! */
 			else
-				return true;
+				return 1;
 		}
 		break;
 	case 2:
 	case 3:
 		/* See 3GPP TS 24.008 10.5.1.6 */
 		if (cm->classmark2_len < 3) {
-			DEBUGP(DMSC, "CLASSMARK 2 unknown, assuming MS doesn't support A5/%u\n", a5);
-			return false;
+			return -2;
 		} else {
 			if (cm->classmark2[2] & (1 << (a5-2)))
-				return true;
+				return 1;
 			else
-				return false;
+				return 0;
 		}
 		break;
 	case 4:
@@ -161,13 +163,12 @@
 	case 7:
 		/* See 3GPP TS 24.008 10.5.1.7 */
 		if (cm->classmark3_len < 1) {
-			DEBUGP(DMSC, "CLASSMARK 3 unknown, assuming MS doesn't support A5/%u\n", a5);
-			return false;
+			return -3;
 		} else {
 			if (cm->classmark3[0] & (1 << (a5-4)))
-				return true;
+				return 1;
 			else
-				return false;
+				return 0;
 		}
 		break;
 	default:
@@ -1592,6 +1593,78 @@
 osmo_static_assert(sizeof(((struct gsm0808_encrypt_info*)0)->key) >= sizeof(((struct osmo_auth_vector*)0)->kc),
 		   gsm0808_encrypt_info_key_fits_osmo_auth_vec_kc);
 
+int msc_geran_set_cipher_mode(struct gsm_subscriber_connection *conn, bool umts_aka, bool retrieve_imeisv)
+{
+	struct gsm_network *net = conn->network;
+	struct gsm0808_encrypt_info ei;
+	int i, j = 0;
+	int request_classmark = 0;
+	int request_classmark_for_a5_n = 0;
+	struct gsm_auth_tuple *tuple = conn->vsub->last_tuple;
+
+	if (!conn || !conn->vsub || !conn->vsub->last_tuple) {
+		/* This should really never happen, because we checked this in msc_vlr_set_ciph_mode()
+		 * already. */
+		LOGP(DMM, LOGL_ERROR, "Internal error: missing state during Ciphering Mode Command\n");
+		return -EINVAL;
+	}
+
+	for (i = 0; i < 8; i++) {
+		int supported;
+
+		/* A5/n permitted by osmo-msc.cfg? */
+		if (!(net->a5_encryption_mask & (1 << i)))
+			continue;
+
+		/* A5/n supported by MS? */
+		supported = classmark_supports_a5(&conn->vsub->classmark, i);
+		if (supported == 1) {
+			ei.perm_algo[j++] = vlr_ciph_to_gsm0808_alg_id(i);
+			/* A higher A5/n is supported, so no need to request a Classmark
+			 * for support of a lesser A5/n. */
+			request_classmark = 0;
+		} else if (supported < 0) {
+			request_classmark = -supported;
+			request_classmark_for_a5_n = i;
+		}
+	}
+	ei.perm_algo_len = j;
+
+	if (request_classmark) {
+		/* The highest A5/n as from osmo-msc.cfg might be available, but we are
+		 * still missing the Classmark information for that from the MS. First
+		 * ask for that. */
+		LOGP(DMM, LOGL_DEBUG, "%s: to determine whether A5/%d is supported,"
+		     " first ask for a Classmark Update to obtain Classmark %d\n",
+		     vlr_subscr_name(conn->vsub), request_classmark_for_a5_n,
+		     request_classmark);
+
+		return msc_classmark_request_then_cipher_mode_cmd(conn, umts_aka, retrieve_imeisv);
+	}
+
+	if (ei.perm_algo_len == 0) {
+		LOGP(DMM, LOGL_ERROR, "%s: cannot start ciphering, no intersection "
+		     "between MSC-configured and MS-supported A5 algorithms\n",
+		     vlr_subscr_name(conn->vsub));
+		return -ENOTSUP;
+	}
+
+	DEBUGP(DMM, "-> CIPHER MODE COMMAND %s\n", vlr_subscr_name(conn->vsub));
+
+	tuple = conn->vsub->last_tuple;
+
+	/* In case of UMTS AKA, the Kc for ciphering must be derived from the 3G auth
+	 * tokens.  tuple->vec.kc was calculated from the GSM algorithm and is not
+	 * necessarily a match for the UMTS AKA tokens. */
+	if (umts_aka)
+		osmo_auth_c3(ei.key, tuple->vec.ck, tuple->vec.ik);
+	else
+		memcpy(ei.key, tuple->vec.kc, sizeof(tuple->vec.kc));
+	ei.key_len = sizeof(tuple->vec.kc);
+
+	return a_iface_tx_cipher_mode(conn, &ei, retrieve_imeisv);
+}
+
 /* VLR asks us to start using ciphering.
  * (Keep non-static to allow regression testing on this function.) */
 int msc_vlr_set_ciph_mode(void *msc_conn_ref,
@@ -1620,38 +1693,7 @@
 
 	switch (conn->via_ran) {
 	case RAN_GERAN_A:
-		DEBUGP(DMM, "-> CIPHER MODE COMMAND %s\n",
-		       vlr_subscr_name(conn->vsub));
-		{
-			struct gsm_network *net = conn->network;
-			struct gsm0808_encrypt_info ei;
-			int i, j = 0;
-
-			for (i = 0; i < 8; i++) {
-				if (net->a5_encryption_mask & (1 << i) &&
-				    classmark_supports_a5(&conn->vsub->classmark, i))
-					ei.perm_algo[j++] = vlr_ciph_to_gsm0808_alg_id(i);
-			}
-			ei.perm_algo_len = j;
-
-			if (ei.perm_algo_len == 0) {
-				LOGP(DMM, LOGL_ERROR, "%s: cannot start ciphering, no intersection "
-					"between MSC-configured and MS-supported A5 algorithms\n",
-					vlr_subscr_name(conn->vsub));
-				return -ENOTSUP;
-			}
-
-			/* In case of UMTS AKA, the Kc for ciphering must be derived from the 3G auth
-			 * tokens.  tuple->vec.kc was calculated from the GSM algorithm and is not
-			 * necessarily a match for the UMTS AKA tokens. */
-			if (umts_aka)
-				osmo_auth_c3(ei.key, tuple->vec.ck, tuple->vec.ik);
-			else
-				memcpy(ei.key, tuple->vec.kc, sizeof(tuple->vec.kc));
-			ei.key_len = sizeof(tuple->vec.kc);
-
-			return a_iface_tx_cipher_mode(conn, &ei, retrieve_imeisv);
-		}
+		return msc_geran_set_cipher_mode(conn, umts_aka, retrieve_imeisv);
 
 	case RAN_UTRAN_IU:
 #ifdef BUILD_IU