All of lore.kernel.org
 help / color / mirror / Atom feed
From: Chris Leech <cleech@redhat.com>
To: linux-nvme@lists.infradead.org
Cc: Hannes Reinecke <hare@suse.de>, Daniel Wagner <dwagner@suse.de>,
	Maurizio Lombardi <mlombard@redhat.com>
Subject: [RFC PATCH] nvme-keyring: add request_key upcalls for dynamic key loading
Date: Wed, 22 Apr 2026 16:10:22 -0700	[thread overview]
Message-ID: <20260422231043.2689681-2-cleech@redhat.com> (raw)
In-Reply-To: <20260422231043.2689681-1-cleech@redhat.com>

This is a prototype of a "PSK request" key type, which requests one or
more PSKs for a host NQN, subsystem NQN pair.  The matching PSKs and
needed metadata to recreate the correct identity are returned from the
request_key upcall, the unpacked into new psk keys in the nvme keyring.

Assisted-by: Claude:claude-sonnet-4.5
---
 drivers/nvme/common/keyring.c | 373 +++++++++++++++++++++++++++++++++-
 1 file changed, 372 insertions(+), 1 deletion(-)

diff --git a/drivers/nvme/common/keyring.c b/drivers/nvme/common/keyring.c
index 32d16c53133be..22fb64cbe16ef 100644
--- a/drivers/nvme/common/keyring.c
+++ b/drivers/nvme/common/keyring.c
@@ -7,12 +7,36 @@
 #include <linux/seq_file.h>
 #include <linux/key-type.h>
 #include <keys/user-type.h>
+#include <linux/base64.h>
 #include <linux/nvme.h>
 #include <linux/nvme-tcp.h>
 #include <linux/nvme-keyring.h>
+#include <linux/nvme-auth.h>
 
 static struct key *nvme_keyring;
 
+/*
+ * Parsed PSK entry from binary payload.
+ */
+struct nvme_psk_entry {
+	u8 version;
+	u8 hmac;
+	u8 key_len;
+	u8 digest_len;
+	u8 key_data[48];
+	u8 digest[48];
+};
+
+/*
+ * Payload for nvme-psk-request key type.
+ * Stores parsed PSK entries from binary payload.
+ */
+struct psk_request_payload {
+	struct rcu_head rcu;
+	int psk_count;
+	struct nvme_psk_entry psk_entries[];
+};
+
 key_serial_t nvme_keyring_id(void)
 {
 	return nvme_keyring->serial;
@@ -77,7 +101,6 @@ static int nvme_tls_psk_match_preparse(struct key_match_data *match_data)
 
 static struct key_type nvme_tls_psk_key_type = {
 	.name           = "psk",
-	.flags          = KEY_TYPE_NET_DOMAIN,
 	.preparse       = user_preparse,
 	.free_preparse  = user_free_preparse,
 	.match_preparse = nvme_tls_psk_match_preparse,
@@ -88,6 +111,193 @@ static struct key_type nvme_tls_psk_key_type = {
 	.read           = user_read,
 };
 
+/*
+ * nvme-psk-request key type callbacks
+ */
+
+/*
+ * parse_binary_psk_payload - Parse binary PSK payload
+ * @payload: Binary PSK payload
+ * @paylen: Length of @payload
+ * @entries: Destination pointer for allocated PSK entry array
+ * @count: Destination for number of PSKs parsed
+ *
+ * Parses a binary payload containing one or more PSKs.
+ * Format per PSK: hmac(1) + key_len(1) + key_data[key_len]
+ *
+ * Returns 0 on success with *entries allocated and *count set.
+ * Returns negative error code if no valid PSKs found.
+ */
+static int parse_binary_psk_payload(const u8 *payload, size_t paylen,
+				     struct nvme_psk_entry **entries,
+				     int *count)
+{
+	const u8 *p = payload;
+	const u8 *end = payload + paylen;
+	struct nvme_psk_entry *temp_entries;
+	int n = 0, idx = 0;
+	const u8 *scan;
+
+	if (!payload || paylen == 0)
+		return -EINVAL;
+
+	/* Count entries first */
+	scan = p;
+	while (scan < end) {
+		u8 key_len, digest_len;
+
+		if (scan + 3 > end)
+			break;
+
+		key_len = scan[1];
+		digest_len = scan[2];
+
+		if (key_len > 48 || digest_len > 48 ||
+		    scan + 3 + key_len + digest_len > end) {
+			pr_debug("invalid PSK entry at offset %ld: key_len=%u digest_len=%u\n",
+				 scan - payload, key_len, digest_len);
+			break;
+		}
+		n++;
+		scan += 3 + key_len + digest_len;
+	}
+
+	if (n == 0)
+		return -EINVAL;
+
+	/* Allocate array */
+	temp_entries = kvmalloc_objs(*temp_entries, n, GFP_KERNEL);
+	if (!temp_entries)
+		return -ENOMEM;
+
+	/* Parse each PSK */
+	while (p < end) {
+		u8 hmac, key_len, digest_len;
+
+		if (p + 3 > end)
+			break;
+
+		hmac = p[0];
+		key_len = p[1];
+		digest_len = p[2];
+		p += 3;
+
+		if (key_len > 48 || digest_len > 48 ||
+		    p + key_len + digest_len > end) {
+			pr_debug("truncated PSK entry: key_len=%u digest_len=%u, remaining=%ld\n",
+				 key_len, digest_len, end - p);
+			break;
+		}
+
+		temp_entries[idx].version = 1;
+		temp_entries[idx].hmac = hmac;
+		temp_entries[idx].key_len = key_len;
+		temp_entries[idx].digest_len = digest_len;
+		memcpy(temp_entries[idx].key_data, p, key_len);
+		p += key_len;
+		memcpy(temp_entries[idx].digest, p, digest_len);
+		p += digest_len;
+		idx++;
+	}
+
+	if (idx == 0) {
+		kvfree(temp_entries);
+		return -EINVAL;
+	}
+
+	*entries = temp_entries;
+	*count = idx;
+	return 0;
+}
+
+static int psk_request_preparse(struct key_preparsed_payload *prep)
+{
+	struct psk_request_payload *payload;
+	struct nvme_psk_entry *entries;
+	int count, ret;
+	size_t payload_size;
+
+	/* Parse binary PSK payload */
+	ret = parse_binary_psk_payload(prep->data, prep->datalen, &entries, &count);
+	if (ret)
+		return ret;
+
+	/* Allocate payload structure with flexible array */
+	payload_size = sizeof(*payload) + count * sizeof(struct nvme_psk_entry);
+	payload = kzalloc(payload_size, GFP_KERNEL);
+	if (!payload) {
+		kvfree(entries);
+		return -ENOMEM;
+	}
+
+	payload->psk_count = count;
+	memcpy(payload->psk_entries, entries, count * sizeof(*entries));
+	kvfree(entries);
+
+	prep->quotalen = payload_size;
+	prep->payload.data[0] = payload;
+
+	return 0;
+}
+
+static void psk_request_free_preparse(struct key_preparsed_payload *prep)
+{
+	kfree(prep->payload.data[0]);
+}
+
+static int psk_request_instantiate(struct key *key,
+				    struct key_preparsed_payload *prep)
+{
+	int ret;
+
+	ret = key_payload_reserve(key, prep->quotalen);
+	if (ret == 0) {
+		rcu_assign_keypointer(key, prep->payload.data[0]);
+		prep->payload.data[0] = NULL;
+	}
+	return ret;
+}
+
+static void psk_request_destroy(struct key *key)
+{
+	struct psk_request_payload *payload;
+
+	payload = rcu_access_pointer(key->payload.rcu_data0);
+	kfree(payload);
+}
+
+static void psk_request_describe(const struct key *key, struct seq_file *m)
+{
+	seq_puts(m, "nvme-psk-request: ");
+	seq_puts(m, key->description);
+}
+
+static long psk_request_read(const struct key *key, char *buffer,
+			      size_t buflen)
+{
+	struct psk_request_payload *payload;
+	long ret;
+
+	payload = dereference_key_locked(key);
+	if (!payload)
+		return -EKEYREVOKED;
+
+	/* Return PSK count for debugging */
+	ret = scnprintf(buffer, buflen, "psk_count=%d\n", payload->psk_count);
+
+	return ret;
+}
+
+static struct key_type nvme_tls_psk_request_key_type = {
+	.name           = "nvme-psk-request",
+	.preparse       = psk_request_preparse,
+	.free_preparse  = psk_request_free_preparse,
+	.instantiate    = psk_request_instantiate,
+	.destroy        = psk_request_destroy,
+	.describe       = psk_request_describe,
+	.read           = psk_request_read,
+};
+
 static struct key *nvme_tls_psk_lookup(struct key *keyring,
 		const char *hostnqn, const char *subnqn,
 		u8 hmac, u8 psk_ver, bool generated)
@@ -123,6 +333,151 @@ static struct key *nvme_tls_psk_lookup(struct key *keyring,
 	return key_ref_to_ptr(keyref);
 }
 
+/*
+ * nvme_tls_psk_request - Request PSK via userspace upcall
+ * @keyring: Target keyring for PSKs
+ * @hostnqn: Host NQN
+ * @subnqn: Subsystem NQN
+ *
+ * Invokes a userspace upcall via request_key() to obtain PSKs.
+ * Userspace returns a binary payload containing one or more PSKs.
+ * Binary format per PSK: hmac(1) + key_len(1) + key_data[key_len]
+ * This function parses the payload and creates PSK keys in the
+ * target keyring with full identities.
+ *
+ * Returns the serial of the first PSK created, or 0 on failure.
+ */
+static key_serial_t nvme_tls_psk_request(struct key *keyring,
+					  const char *hostnqn,
+					  const char *subnqn)
+{
+	char *description = NULL;
+	char *callout_info = NULL;
+	size_t description_len, callout_len;
+	struct key *req_key = NULL;
+	struct psk_request_payload *payload;
+	key_serial_t first_psk_serial = 0;
+	key_serial_t keyring_id;
+	int i;
+
+	if (!keyring)
+		keyring = nvme_keyring;
+	keyring_id = key_serial(keyring);
+
+	/* Build simple description: "<hostnqn> <subnqn>" */
+	description_len = strlen(hostnqn) + strlen(subnqn) + 2;
+	description = kzalloc(description_len, GFP_KERNEL);
+	if (!description)
+		return 0;
+
+	snprintf(description, description_len, "%s %s", hostnqn, subnqn);
+
+	/* Build callout info: "hostnqn=... subnqn=..." */
+	callout_len = strlen("hostnqn=") + strlen(hostnqn) +
+		      strlen(" subnqn=") + strlen(subnqn) + 1;
+	callout_info = kzalloc(callout_len, GFP_KERNEL);
+	if (!callout_info) {
+		kfree(description);
+		return 0;
+	}
+
+	snprintf(callout_info, callout_len, "hostnqn=%s subnqn=%s",
+		 hostnqn, subnqn);
+
+	pr_debug("keyring %x request PSKs for '%s'\n", keyring_id, description);
+
+	/* Request key from userspace */
+	req_key = request_key(&nvme_tls_psk_request_key_type,
+			       description, callout_info);
+	if (IS_ERR(req_key)) {
+		pr_debug("request_key failed for '%s', error %ld\n",
+			 description, PTR_ERR(req_key));
+		goto out_free;
+	}
+
+	/* Extract parsed PSKs from request key payload */
+	down_read(&req_key->sem);
+	payload = dereference_key_locked(req_key);
+	if (!payload) {
+		up_read(&req_key->sem);
+		pr_debug("request key has no payload\n");
+		goto out_put_req;
+	}
+
+	pr_debug("received %d PSK(s) from upcall\n", payload->psk_count);
+
+	/* Create each PSK in target keyring */
+	for (i = 0; i < payload->psk_count; i++) {
+		struct nvme_psk_entry *psk = &payload->psk_entries[i];
+		char *identity = NULL;
+		char *digest_b64 = NULL;
+		key_ref_t keyref;
+		struct key *key;
+		key_perm_t keyperm = KEY_POS_SEARCH | KEY_POS_VIEW |
+				     KEY_POS_READ | KEY_POS_WRITE |
+				     KEY_POS_LINK | KEY_POS_SETATTR |
+				     KEY_USR_SEARCH | KEY_USR_VIEW |
+				     KEY_USR_READ;
+		int digest_b64_len;
+
+		/* Convert raw digest to base64 string for identity */
+		digest_b64 = kmalloc(BASE64_CHARS(psk->digest_len) + 1, GFP_KERNEL);
+		if (!digest_b64)
+			continue;
+
+		digest_b64_len = base64_encode(psk->digest, psk->digest_len,
+					       digest_b64, false, BASE64_STD);
+		digest_b64[digest_b64_len] = '\0';
+
+		/* Build identity: "NVMe<v>R<hh> <hostnqn> <subnqn> <digest>" */
+		identity = kasprintf(GFP_KERNEL, "NVMe%uR%02u %s %s %s",
+				     psk->version, psk->hmac, hostnqn,
+				     subnqn, digest_b64);
+		if (!identity) {
+			kfree(digest_b64);
+			continue;
+		}
+
+		/* Create PSK key in target keyring */
+		keyref = key_create_or_update(make_key_ref(keyring, true),
+					"psk", identity, psk->key_data,
+					psk->key_len, keyperm,
+					KEY_ALLOC_NOT_IN_QUOTA |
+					KEY_ALLOC_BUILT_IN |
+					KEY_ALLOC_BYPASS_RESTRICTION);
+
+		if (!IS_ERR(keyref)) {
+			key = key_ref_to_ptr(keyref);
+			pr_debug("created PSK %x '%s' in keyring %x\n",
+				 key->serial, identity, keyring_id);
+			if (first_psk_serial == 0)
+				first_psk_serial = key->serial;
+			key_put(key);
+		} else {
+			pr_debug("failed to create PSK '%s': %ld\n",
+				 identity, PTR_ERR(keyref));
+		}
+
+		kfree(digest_b64);
+		kfree(identity);
+	}
+
+	up_read(&req_key->sem);
+
+	if (first_psk_serial)
+		pr_debug("obtained %d PSK(s) via upcall, first: %08x\n",
+			 payload->psk_count, first_psk_serial);
+	else
+		pr_debug("PSK upcall returned no valid PSKs\n");
+
+out_put_req:
+	key_put(req_key);
+out_free:
+	kfree(callout_info);
+	kfree(description);
+	return first_psk_serial;
+}
+
 /**
  * nvme_tls_psk_refresh - Refresh TLS PSK
  * @keyring: Keyring holding the TLS PSK
@@ -235,6 +590,7 @@ key_serial_t nvme_tls_psk_default(struct key *keyring,
 	key_serial_t tls_key_id;
 	int prio;
 
+	/* First, search the keyring for existing PSKs (fast path) */
 	for (prio = 0; prio < ARRAY_SIZE(nvme_tls_psk_prio); prio++) {
 		bool generated = nvme_tls_psk_prio[prio].generated;
 		u8 ver = nvme_tls_psk_prio[prio].psk_ver;
@@ -248,6 +604,12 @@ key_serial_t nvme_tls_psk_default(struct key *keyring,
 			return tls_key_id;
 		}
 	}
+
+	/* If not found in keyring, try userspace upcall (slow path) */
+	tls_key_id = nvme_tls_psk_request(keyring, hostnqn, subnqn);
+	if (tls_key_id)
+		return tls_key_id;
+
 	return 0;
 }
 EXPORT_SYMBOL_GPL(nvme_tls_psk_default);
@@ -270,11 +632,20 @@ static int __init nvme_keyring_init(void)
 		key_put(nvme_keyring);
 		return err;
 	}
+
+	err = register_key_type(&nvme_tls_psk_request_key_type);
+	if (err) {
+		unregister_key_type(&nvme_tls_psk_key_type);
+		key_put(nvme_keyring);
+		return err;
+	}
+
 	return 0;
 }
 
 static void __exit nvme_keyring_exit(void)
 {
+	unregister_key_type(&nvme_tls_psk_request_key_type);
 	unregister_key_type(&nvme_tls_psk_key_type);
 	key_revoke(nvme_keyring);
 	key_put(nvme_keyring);
-- 
2.53.0



  reply	other threads:[~2026-04-22 23:11 UTC|newest]

Thread overview: 5+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-04-22 23:10 [RFC nvme-keyring nvme-cli] Should NVMe/TLS PSKs support the request_key API? Chris Leech
2026-04-22 23:10 ` Chris Leech [this message]
2026-04-22 23:10 ` [RFC] example request-key helper script Chris Leech
2026-04-23 12:05 ` [RFC nvme-keyring nvme-cli] Should NVMe/TLS PSKs support the request_key API? Hannes Reinecke
2026-04-23 17:15   ` Daniel Wagner

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260422231043.2689681-2-cleech@redhat.com \
    --to=cleech@redhat.com \
    --cc=dwagner@suse.de \
    --cc=hare@suse.de \
    --cc=linux-nvme@lists.infradead.org \
    --cc=mlombard@redhat.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.