db: extend database schema to support 256bit K and/or OP[c] values

Other UMTS AKA algorithms than MILENAGE (notably TUAK) support K sizes
of up to 256bit, or mandate a OP/OPc size of 256 bit.

Let's extend our database schema to accommodate such larger sizes.

Change-Id: Ibbde68484c904507a15c35cbfdf88cd47d0c7039
diff --git a/src/ctrl.c b/src/ctrl.c
index f0f2ee6..e04195d 100644
--- a/src/ctrl.c
+++ b/src/ctrl.c
@@ -39,7 +39,7 @@
 #define SEL_BY_ID SEL_BY "id-"
 
 extern bool auth_algo_parse(const char *alg_str, enum osmo_auth_algo *algo,
-			    int *minlen, int *maxlen);
+			    int *minlen, int *maxlen, int *minlen_opc, int *maxlen_opc);
 
 #define hexdump_buf(buf) osmo_hexdump_nospc((void*)buf, sizeof(buf))
 
@@ -550,7 +550,7 @@
 	}
 	if (strcmp(tok, "none") == 0) {
 		aud2g.algo = OSMO_AUTH_ALG_NONE;
-	} else if (!auth_algo_parse(tok, &aud2g.algo, &minlen, &maxlen)) {
+	} else if (!auth_algo_parse(tok, &aud2g.algo, &minlen, &maxlen, NULL, NULL)) {
 		cmd->reply = "Unknown auth algorithm.";
 		return CTRL_CMD_ERROR;
 	}
@@ -630,8 +630,8 @@
 	struct hlr *hlr = data;
 	const char *by_selector = cmd->node;
 	char *tmp = NULL, *tok, *saveptr;
-	int minlen = 0;
-	int maxlen = 0;
+	int minlen = 0, minlen_opc = 0;
+	int maxlen = 0, maxlen_opc = 0;
 	struct sub_auth_data_str aud3g = {
 		.type = OSMO_AUTH_TYPE_UMTS,
 		.u.umts = {
@@ -657,7 +657,7 @@
 	}
 	if (strcmp(tok, "none") == 0) {
 		aud3g.algo = OSMO_AUTH_ALG_NONE;
-	} else if (!auth_algo_parse(tok, &aud3g.algo, &minlen, &maxlen)) {
+	} else if (!auth_algo_parse(tok, &aud3g.algo, &minlen, &maxlen, &minlen_opc, &maxlen_opc)) {
 		cmd->reply = "Unknown auth algorithm.";
 		return CTRL_CMD_ERROR;
 	}
@@ -699,7 +699,7 @@
 		}
 
 		aud3g.u.umts.opc = tok;
-		if (!osmo_is_hexstr(aud3g.u.umts.opc, MILENAGE_KEY_LEN * 2, MILENAGE_KEY_LEN * 2, true)) {
+		if (!osmo_is_hexstr(aud3g.u.umts.opc, minlen_opc * 2, maxlen_opc * 2, true)) {
 			cmd->reply = talloc_asprintf(cmd, "Invalid OP/OPC.");
 			return CTRL_CMD_ERROR;
 		}
diff --git a/src/db.c b/src/db.c
index cfe34c3..7b8a415 100644
--- a/src/db.c
+++ b/src/db.c
@@ -1,4 +1,4 @@
-/* (C) 2015 by Harald Welte <laforge@gnumonks.org>
+/* (C) 2015-2023 by Harald Welte <laforge@gnumonks.org>
  *
  * All Rights Reserved
  *
@@ -28,7 +28,7 @@
 #include "db_bootstrap.h"
 
 /* This constant is currently duplicated in sql/hlr.sql and must be kept in sync! */
-#define CURRENT_SCHEMA_VERSION	6
+#define CURRENT_SCHEMA_VERSION	7
 
 #define SEL_COLUMNS \
 	"id," \
@@ -513,6 +513,46 @@
 	return rc;
 }
 
+static int db_upgrade_v7(struct db_context *dbc)
+{
+	int rc;
+	/* SQLite doesn't allow us to change the column type in-place, so we
+	 * first rename the old table, create a new table and then copy
+	 * the data over before deleting the old table */
+#define CREATE_AUC_3G_V7	\
+"CREATE TABLE auc_3g (\n" \
+"	subscriber_id	INTEGER PRIMARY KEY,	-- subscriber.id\n" \
+"	algo_id_3g	INTEGER NOT NULL,	-- enum osmo_auth_algo value\n" \
+"	k		VARCHAR(64) NOT NULL,	-- hex string: subscriber's secret key (128/256bit)\n" \
+"	op		VARCHAR(64),		-- hex string: operator's secret key (128/256bit)\n" \
+"	opc		VARCHAR(64),		-- hex string: derived from OP and K (128/256bit)\n" \
+"	sqn		INTEGER NOT NULL DEFAULT 0,	-- sequence number of key usage\n" \
+"	-- nr of index bits at lower SQN end\n" \
+"	ind_bitlen	INTEGER NOT NULL DEFAULT 5\n" \
+");"
+	const char * const statements[] = {
+		"BEGIN TRANSACTION",
+		/* rename old table */
+		"ALTER TABLE auc_3g RENAME TO old_auc_3g",
+		/* create new table */
+		CREATE_AUC_3G_V7,
+		/* copy over old data */
+		"INSERT INTO auc_3g SELECT subscriber_id, algo_id_3g, k, op, opc,sqn, ind_bitlen FROM old_auc_3g",
+		/* delete old table */
+		"DROP TABLE old_auc_3g",
+		/* update user_version */
+		"PRAGMA user_version = 7",
+		"COMMIT",
+	};
+
+	rc = db_run_statements(dbc, statements, ARRAY_SIZE(statements));
+	if (rc != SQLITE_DONE) {
+		LOGP(DDB, LOGL_ERROR, "Unable to update HLR database schema to version 7\n");
+		return rc;
+	}
+	return rc;
+}
+
 typedef int (*db_upgrade_func_t)(struct db_context *dbc);
 static db_upgrade_func_t db_upgrade_path[] = {
 	db_upgrade_v1,
@@ -521,6 +561,7 @@
 	db_upgrade_v4,
 	db_upgrade_v5,
 	db_upgrade_v6,
+	db_upgrade_v7,
 };
 
 static int db_get_user_version(struct db_context *dbc)
diff --git a/src/db_hlr.c b/src/db_hlr.c
index 8dfbb15..aa2e365 100644
--- a/src/db_hlr.c
+++ b/src/db_hlr.c
@@ -1,4 +1,4 @@
-/* (C) 2015 by Harald Welte <laforge@gnumonks.org>
+/* (C) 2015-2023 by Harald Welte <laforge@gnumonks.org>
  *
  * All Rights Reserved
  *
@@ -286,12 +286,12 @@
 
 		if (aud->algo == OSMO_AUTH_ALG_NONE)
 			break;
-		if (!osmo_is_hexstr(aud->u.umts.k, 32, 32, true)) {
+		if (!osmo_is_hexstr(aud->u.umts.k, 32, 64, true)) {
 			LOGP(DAUC, LOGL_ERROR, "Cannot update auth tokens:"
 			     " Invalid K: '%s'\n", aud->u.umts.k);
 			return -EINVAL;
 		}
-		if (!osmo_is_hexstr(aud->u.umts.opc, 32, 32, true)) {
+		if (!osmo_is_hexstr(aud->u.umts.opc, 32, 64, true)) {
 			LOGP(DAUC, LOGL_ERROR, "Cannot update auth tokens:"
 			     " Invalid OP/OPC: '%s'\n", aud->u.umts.opc);
 			return -EINVAL;
diff --git a/src/hlr_vty_subscr.c b/src/hlr_vty_subscr.c
index 191a87d..28cc5b3 100644
--- a/src/hlr_vty_subscr.c
+++ b/src/hlr_vty_subscr.c
@@ -473,8 +473,14 @@
 	"Use Milenage algorithm\n"
 
 bool auth_algo_parse(const char *alg_str, enum osmo_auth_algo *algo,
-		     int *minlen, int *maxlen)
+		     int *minlen, int *maxlen, int *minlen_opc, int *maxlen_opc)
 {
+	/* Default: no OP[c]. True for all 2G algorithms, and 3G-XOR. Overridden below for real 3G AKA algorithms. */
+	if (minlen_opc)
+		*minlen_opc = 0;
+	if (maxlen_opc)
+		*maxlen_opc = 0;
+
 	if (!strcasecmp(alg_str, "none")) {
 		*algo = OSMO_AUTH_ALG_NONE;
 		*minlen = *maxlen = 0;
@@ -497,6 +503,10 @@
 	} else if (!strcasecmp(alg_str, "milenage")) {
 		*algo = OSMO_AUTH_ALG_MILENAGE;
 		*minlen = *maxlen = MILENAGE_KEY_LEN;
+		if (minlen_opc)
+			*minlen_opc = MILENAGE_KEY_LEN;
+		if (maxlen_opc)
+			*maxlen_opc = MILENAGE_KEY_LEN;
 	} else
 		return false;
 	return true;
@@ -552,7 +562,7 @@
 		.u.gsm.ki = ki,
 	};
 
-	if (!auth_algo_parse(alg_type, &aud2g.algo, &minlen, &maxlen)) {
+	if (!auth_algo_parse(alg_type, &aud2g.algo, &minlen, &maxlen, NULL, NULL)) {
 		vty_out(vty, "%% Unknown auth algorithm: '%s'%s", alg_type, VTY_NEWLINE);
 		return CMD_WARNING;
 	}
@@ -611,13 +621,13 @@
       SUBSCR_UPDATE_HELP
       "Set UMTS authentication data (3G, and 2G with UMTS AKA)\n"
       AUTH_ALG_TYPES_3G_HELP
-      "Set Encryption Key K\n" "K as 32 hexadecimal characters\n"
-      "Set OP key\n" "Set OPC key\n" "OP or OPC as 32 hexadecimal characters\n"
+      "Set Encryption Key K\n" "K as 32/64 hexadecimal characters\n"
+      "Set OP key\n" "Set OPC key\n" "OP or OPC as 32/64 hexadecimal characters\n"
       "Set IND bit length\n" "IND bit length value (default: 5)\n")
 {
 	struct hlr_subscriber subscr;
-	int minlen = 0;
-	int maxlen = 0;
+	int minlen = 0, minlen_opc = 0;
+	int maxlen = 0, maxlen_opc = 0;
 	int rc;
 	const char *id_type = argv[0];
 	const char *id = argv[1];
@@ -636,7 +646,7 @@
 		},
 	};
 
-	if (!auth_algo_parse(alg_type, &aud3g.algo, &minlen, &maxlen)) {
+	if (!auth_algo_parse(alg_type, &aud3g.algo, &minlen, &maxlen, &minlen_opc, &maxlen_opc)) {
 		vty_out(vty, "%% Unknown auth algorithm: '%s'%s", alg_type, VTY_NEWLINE);
 		return CMD_WARNING;
 	}
@@ -644,8 +654,7 @@
 	if (!is_hexkey_valid(vty, "K", aud3g.u.umts.k, minlen, maxlen))
 		return CMD_WARNING;
 
-	if (!is_hexkey_valid(vty, opc_is_op ? "OP" : "OPC", aud3g.u.umts.opc,
-			     MILENAGE_KEY_LEN, MILENAGE_KEY_LEN))
+	if (!is_hexkey_valid(vty, opc_is_op ? "OP" : "OPC", aud3g.u.umts.opc, minlen_opc, maxlen_opc))
 		return CMD_WARNING;
 
 	if (get_subscr_by_argv(vty, id_type, id, &subscr))
@@ -689,7 +698,7 @@
 		},
 	};
 
-	if (!auth_algo_parse("xor-3g", &aud3g.algo, &minlen, &maxlen)) {
+	if (!auth_algo_parse("xor-3g", &aud3g.algo, &minlen, &maxlen, NULL, NULL)) {
 		vty_out(vty, "%% Unknown auth algorithm: '%s'%s", "xor-3g", VTY_NEWLINE);
 		return CMD_WARNING;
 	}