From 9ce8c6ced68d45b462abb4c6531b6476f4d1e681 Mon Sep 17 00:00:00 2001
From: Jonathan McDowell <noodles@earth.li>
Date: Wed, 21 Aug 2019 18:56:57 +0100
Subject: [PATCH] Add option to only accept updates for existing keys

It's plausible in a curated keyring scenario where a keyserver should
accept updates to existing keys (new signatures, updated expiries, new
subkeys) but no entirely new keys. Add a configuration file option which
enforces this behaviour.
---
 cgi/add.c               |  4 +++-
 cleankey.h              |  1 +
 keydb.c                 | 17 ++++++++++++-----
 keydb.h                 |  2 ++
 keydb_dynamic.c         |  3 ++-
 keydb_keyring.c         |  1 +
 keydb_stacked.c         |  4 +++-
 onak-conf.c             |  9 +++++++++
 onak.c                  |  2 ++
 onak.ini.in             |  4 ++++
 t/all-036-update-only.t | 17 +++++++++++++++++
 11 files changed, 56 insertions(+), 8 deletions(-)
 create mode 100755 t/all-036-update-only.t

diff --git a/cgi/add.c b/cgi/add.c
index 02eb3eb..e17d3d9 100644
--- a/cgi/add.c
+++ b/cgi/add.c
@@ -93,7 +93,9 @@ int main(int argc, char *argv[])
 					count);
 
 			count = dbctx->update_keys(dbctx, &keys,
-					&config.blacklist, true);
+				&config.blacklist,
+				config.clean_policies & ONAK_CLEAN_UPDATE_ONLY,
+				true);
 			logthing(LOGTHING_NOTICE, "Got %d new keys.",
 				count);
 
diff --git a/cleankey.h b/cleankey.h
index 834b622..22a7e45 100644
--- a/cleankey.h
+++ b/cleankey.h
@@ -24,6 +24,7 @@
 #define ONAK_CLEAN_CHECK_SIGHASH	(1 << 0)
 #define ONAK_CLEAN_LARGE_PACKETS	(1 << 1)
 #define ONAK_CLEAN_DROP_V3_KEYS		(1 << 2)
+#define ONAK_CLEAN_UPDATE_ONLY		(1 << 3)
 #define ONAK_CLEAN_ALL			(uint64_t) -1
 
 /**
diff --git a/keydb.c b/keydb.c
index f362b1a..f6b9682 100644
--- a/keydb.c
+++ b/keydb.c
@@ -159,6 +159,8 @@ struct ll *generic_cached_getkeysigs(struct onak_dbctx *dbctx, uint64_t keyid)
 /**
  *	update_keys - Takes a list of public keys and updates them in the DB.
  *	@keys: The keys to update in the DB.
+ *	@blacklist: A keyarray of key fingerprints not to accept.
+ *	@updateonly: Only update existing keys, don't add new ones.
  *	@sendsync: Should we send a sync mail to our peers.
  *
  *	Takes a list of keys and adds them to the database, merging them with
@@ -170,12 +172,13 @@ struct ll *generic_cached_getkeysigs(struct onak_dbctx *dbctx, uint64_t keyid)
 int generic_update_keys(struct onak_dbctx *dbctx,
 		struct openpgp_publickey **keys,
 		struct keyarray *blacklist,
+		bool updateonly,
 		bool sendsync)
 {
 	struct openpgp_publickey **curkey, *tmp = NULL;
 	struct openpgp_publickey *oldkey = NULL;
 	struct openpgp_fingerprint fp;
-	int newkeys = 0;
+	int newkeys = 0, ret;
 	bool intrans;
 
 	curkey = keys;
@@ -192,10 +195,13 @@ int generic_update_keys(struct onak_dbctx *dbctx,
 
 		intrans = dbctx->starttrans(dbctx);
 
-		logthing(LOGTHING_INFO,
-			"Fetching key, result: %d",
-			dbctx->fetch_key_fp(dbctx, &fp, &oldkey,
-					intrans));
+		ret = dbctx->fetch_key_fp(dbctx, &fp, &oldkey, intrans);
+		if (ret == 0 && updateonly) {
+			logthing(LOGTHING_INFO,
+				"Skipping new key as update only set.");
+			curkey = &(*curkey)->next;
+			goto next;
+		}
 
 		/*
 		 * If we already have the key stored in the DB then merge it
@@ -228,6 +234,7 @@ int generic_update_keys(struct onak_dbctx *dbctx,
 			newkeys++;
 			curkey = &(*curkey)->next;
 		}
+next:
 		dbctx->endtrans(dbctx);
 	}
 
diff --git a/keydb.h b/keydb.h
index a1078c7..7297a76 100644
--- a/keydb.h
+++ b/keydb.h
@@ -141,6 +141,7 @@ struct onak_dbctx {
  * @brief Takes a list of public keys and updates them in the DB.
  * @param keys The keys to update in the DB.
  * @param blacklist A keyarray of fingerprints that shouldn't be added.
+ * @updateonly: Only update existing keys, don't add new ones.
  * @param sendsync If we should send a keysync mail.
  *
  * Takes a list of keys and adds them to the database, merging them with
@@ -155,6 +156,7 @@ struct onak_dbctx {
 	int (*update_keys)(struct onak_dbctx *,
 			struct openpgp_publickey **keys,
 			struct keyarray *blacklist,
+			bool updateonly,
 			bool sendsync);
 
 /**
diff --git a/keydb_dynamic.c b/keydb_dynamic.c
index cf9d87f..385c09c 100644
--- a/keydb_dynamic.c
+++ b/keydb_dynamic.c
@@ -122,13 +122,14 @@ static int dynamic_delete_key(struct onak_dbctx *dbctx,
 static int dynamic_update_keys(struct onak_dbctx *dbctx,
 		struct openpgp_publickey **keys,
 		struct keyarray *blacklist,
+		bool updateonly,
 		bool sendsync)
 {
 	struct onak_dynamic_dbctx *privctx =
 			(struct onak_dynamic_dbctx *) dbctx->priv;
 
 	return privctx->loadeddbctx->update_keys(privctx->loadeddbctx,
-			keys, blacklist, sendsync);
+			keys, blacklist, updateonly, sendsync);
 }
 
 static struct ll *dynamic_getkeysigs(struct onak_dbctx *dbctx,
diff --git a/keydb_keyring.c b/keydb_keyring.c
index 0f24366..9550434 100644
--- a/keydb_keyring.c
+++ b/keydb_keyring.c
@@ -224,6 +224,7 @@ static int keyring_iterate_keys(struct onak_dbctx *dbctx,
 static int keyring_update_keys(struct onak_dbctx *dbctx,
 		struct openpgp_publickey **keys,
 		struct keyarray *blacklist,
+		bool updateonly,
 		bool sendsync)
 {
 	return 0;
diff --git a/keydb_stacked.c b/keydb_stacked.c
index b702c84..7e997d9 100644
--- a/keydb_stacked.c
+++ b/keydb_stacked.c
@@ -87,6 +87,7 @@ static int stacked_delete_key(struct onak_dbctx *dbctx,
 static int stacked_update_keys(struct onak_dbctx *dbctx,
 		struct openpgp_publickey **keys,
 		struct keyarray *blacklist,
+		bool updateonly,
 		bool sendsync)
 {
 	struct onak_stacked_dbctx *privctx =
@@ -94,7 +95,8 @@ static int stacked_update_keys(struct onak_dbctx *dbctx,
 	struct onak_dbctx *backend =
 			(struct onak_dbctx *) privctx->backends->object;
 
-	return backend->update_keys(backend, keys, blacklist, sendsync);
+	return backend->update_keys(backend, keys, blacklist, updateonly,
+			sendsync);
 }
 
 static int stacked_iterate_keys(struct onak_dbctx *dbctx,
diff --git a/onak-conf.c b/onak-conf.c
index f0768a0..a67c950 100644
--- a/onak-conf.c
+++ b/onak-conf.c
@@ -313,6 +313,15 @@ static bool parseconfigline(char *line)
 				config.clean_policies &=
 					~ONAK_CLEAN_LARGE_PACKETS;
 			}
+		} else if (MATCH("verification", "update_only")) {
+			if (parsebool(value, config.clean_policies &
+					ONAK_CLEAN_UPDATE_ONLY)) {
+				config.clean_policies |=
+					ONAK_CLEAN_UPDATE_ONLY;
+			} else {
+				config.clean_policies &=
+					~ONAK_CLEAN_UPDATE_ONLY;
+			}
 		} else {
 			return false;
 		}
diff --git a/onak.c b/onak.c
index 70f5434..030d468 100644
--- a/onak.c
+++ b/onak.c
@@ -242,6 +242,8 @@ int main(int argc, char *argv[])
 			logthing(LOGTHING_NOTICE, "Got %d new keys.",
 					dbctx->update_keys(dbctx, &keys,
 						&config.blacklist,
+						(config.clean_policies &
+						 ONAK_CLEAN_UPDATE_ONLY),
 						false));
 			if (keys != NULL && update) {
 				flatten_publickey(keys,
diff --git a/onak.ini.in b/onak.ini.in
index a9745cd..dc68e1f 100644
--- a/onak.ini.in
+++ b/onak.ini.in
@@ -25,6 +25,10 @@ check_sighash=true
 ; Drop v3 (and older) keys. These are long considered insecure, so unless there
 ; is a good reason you should accept this default.
 drop_v3=true
+; Only allow keys that already exist to be update; silently drop the addition
+; of any key we don't already know about. Useful for allowing updates to
+; curated keys without the addition of new keys.
+;update_only=false
 
 ; Settings related to the email interface to onak.
 [mail]
diff --git a/t/all-036-update-only.t b/t/all-036-update-only.t
new file mode 100755
index 0000000..e0b3027
--- /dev/null
+++ b/t/all-036-update-only.t
@@ -0,0 +1,17 @@
+#!/bin/sh
+# Check we can't submit a new key when update_only is set
+
+set -e
+
+cd ${WORKDIR}
+cp $1 update-only.ini
+echo update_only=true >> update-only.ini
+${BUILDDIR}/onak -b -c update-only.ini add < ${TESTSDIR}/../keys/noodles.key || true
+rm update-only.ini
+if ! ${BUILDDIR}/onak -c $1 get 0x94FA372B2DA8B985 2>&1 | \
+	grep -q 'Key not found'; then
+	echo "* Did not correctly error on update-only key"
+	exit 1
+fi
+
+exit 0
-- 
2.39.5